Divide by Zero 2021 and Codeforces Round #714 (Div. 2) D. GCD and MST
D. GCD and MST
题意
给定一个大小为n(n>2)的正整数数组a,给定一个正整数p。如果gcd(ai,ai+1,ai+2,…,aj)=min(ai,ai+1,ai+2,…,aj),那么就可以在i与j之间建立一条权值为min(ai,ai+1,ai+2,…,aj)或p的边,如果不相等则可以建立一条只能建立权值为p的边。问:该图的最小生成树的权值和是多少?
思路
该题的思路类似kruskal求最小生成树的过程,也就是每次去寻找权值最小的边,判断将该边加入后是否形成了环,如果没有则将该边加入。
根据这个思想,这个题就可以简化为如何去寻找最小的边的问题,解决办法就是,将a数组按值进行从小到大排序,对于排列后的数组进行遍历,①如果当前值大于p说明通过它建的边一定大于p,直接退出循环;②如果当前值小于等于p,那么从该值在未排序的a所在的位置x,分别向左,向右判断(判断条件见代码),并把满足条件的标记,将权值加入答案;③对于已经标记过的点直接跳过。
最后将还未加入的点通过建立权值为p的边将其加入。
AC代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<ll,ll> P;
ll aa[200010],vis[200010];
int main()
{
int T;
cin>>T;
while(T--)
{
vector<P> bb;
memset(vis,0,sizeof(vis));
ll n,p;
cin>>n>>p;
for(int i=1;i<=n;i++)
{
cin>>aa[i];
bb.push_back(P(aa[i],i));
}
ll sum=n-1,ans=0;
sort(bb.begin(),bb.end());
for(int i=0;i<n;i++)
{
if(bb[i].first>p) break;
ll x=bb[i].second;
if(vis[x]) continue;
ll l=x,r=x;
while(l>1&&!vis[l]&&aa[l-1]%aa[x]==0) l--;
while(r<n&&!vis[r]&&aa[r+1]%aa[x]==0) r++;
ans+=aa[x]*(r-l);
sum-=r-l;
for(ll j=l;j<=r;j++)
vis[j]=1;
}
ans+=p*sum;
cout<<ans<<endl;
}
}