D - GCD and MST_i++

​link​

思路:

暴力加剪枝。我们可以发现,答案肯定<=(n-1)*p,所以对于gcd大于等于p的区间我们可以不用考虑他带来的贡献,根据克鲁斯卡尔贪心的原则,我们可以按照点权来排序,从小到大枚举,然后嗯往左和右拓展,直到不能拓展或者找到在相同集合的数为止或者区间gcd不是当前数为止。为啥能在相同集合的时候break呢?假设当前左右区间是[i,j],i和j属于同一个集合,由于在相同集合,肯定会有更小的gcd连接他俩,对于左区间,能满足当前的gcd,肯定也能满足更小的_gcd,所以再往下拓展是没有必要的。

代码:

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=200010;
const int mod=1e9+7;
map<int,int>mp;
#define x first
#define y second
int fact[N];
pair<int,int>a[N];
int n;
int m;
int p[N];
int find(int x)
{
if(p[x]!=x)
p[x]=find(p[x]);
return p[x];
}
int b[N];
signed main()
{
// #ifndef ONLINE_JUDGE
// freopen("1.in","r",stdin);
// //freopen("1.out","w",stdout);
// #endif
ios::sync_with_stdio(0);
int t;
cin>>t;
while(t--)
{
int n,c;
cin>>n>>c;
for(int i=1;i<=n;i++) p[i]=i;
for(int i=1;i<=n;i++) cin>>a[i].x,a[i].y=i,b[i]=a[i].x;
sort(a+1,a+1+n);
int ans=(n-1)*c;
// cout<<ans<<endl;
for(int i=1;i<=n;i++)
{
int pos=a[i].y;
int val=a[i].x;
if(val>=c) break;

for(int j=pos-1;j>=1;j--)
{
if(__gcd(b[j],val)!=val) break;
int pj=find(j);
int pi=find(pos);
if(pi==pj) break;
p[pi]=pj;
ans-=c;
ans+=val;
}
for(int j=pos+1;j<=n;j++)
{
if(__gcd(b[j],val)!=val) break;
int pj=find(j);
int pi=find(pos);
if(pi==pj) break;
p[pi]=pj;
ans-=c;
ans+=val;
}
}

cout<<ans<<endl;

}
return 0;
}