考虑最坏情况, n n n个节点彼此由相邻的权值为 p p p的边构成最小生成树
此时答案 a n s = ( n − 1 ) ∗ p ans=(n-1)*p ans=(n−1)∗p
考虑如何减小答案。
设一段区间 [ l , r ] [l,r] [l,r]内的最小数字是 x x x且其余数字都是 x x x的倍数
那么证明 [ l , r − 1 ] [l,r-1] [l,r−1]都可以向 r r r节点连权值为 x x x的边
如果 x < p x<p x<p,那么就可以减小答案。
考虑用优先队列,每次弹出当前最小的数字,设索引为 u u u
那么 u u u往左边一直扩展到 l l l,满足 [ l , u − 1 ] [l,u-1] [l,u−1]的所有数字是 a u a_u au的倍数
u u u往右边一直扩展到 r r r,满足 [ u + 1 , r ] [u+1,r] [u+1,r]的所有数字是 a u a_u au的倍数
那么区间 [ l , r ] [l,r] [l,r]内的 r − l r-l r−l条边都可以由权值为 a u a_u au的边替换
而且,我们可以把 [ l , r ] [l,r] [l,r]的边标记起来,之后扩展的时候就不需要扩展这些边了
至此,我们可以在 O ( n l o g ( n ) ) O(nlog(n)) O(nlog(n))的时间内得到解,瓶颈是优先队列的
#include <bits/stdc++.h>
using namespace std;
const int maxn = 2e5+19;
const int mod = 1e9+7;
int n,base,a[maxn],vis[maxn];//i->i+1的边是否被覆盖
typedef pair<int,int>p;
priority_queue<p,vector<p>,greater<p> >q;
signed main()
{
int t; cin >> t;
while( t-- )
{
cin >> n >> base;
long long ans = 1ll*( 1ll*n-1 )*base;
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i] );
q.push( p(a[i],i) );
}
while( !q.empty() )
{
int u = q.top().second, val = q.top().first; q.pop();
if( val>=base ) break;
int r = u;
while( r+1<=n && a[r+1]%val==0 && vis[r]==0 ) vis[r] = 1,r++;
int l = u;
while( l-1>=1 && a[l-1]%val==0 && vis[l-1]==0 ) l--,vis[l] = 1;
ans = ans-1ll*(r-l)*base+1ll*(r-l)*val;
}
while( !q.empty() ) q.pop();
cout << ans << endl;
for(int i=1;i<=n;i++) vis[i] = 0;
}
}