题意
在一个长度为n的数组之间建立一个最小生成树,默认相邻两点间的距离是p。如果两个点i,j(i < j)之间满足 gcd(ai,ai+1,…aj) = min(ai,ai+1,…aj)。则ai到aj 之间有一条长度为gcd(ai,ai+1,…aj)的路,求最小生成树的总长度。
思路
满足最大长度的 gcd(ai,ai+1,…aj) = min(ai,ai+1,…aj) 这个值如果为ak 那么i,j之间每个点(除了k)到k都存在一条ak的边,且他们都是ak的倍数。且ai-1,aj+1一定不是ak的倍数。
一般最小生成树使用克鲁斯卡尔算法,从最短边开始枚举,按照这种思路。从最小的ai,枚举到最大的小于p的边,依次判断两边是否为ai的倍数,如果是则存在这条边,在枚举的时候用并查集判断联通。因为每次枚举都是一个区间,且边从小到大枚举是不存在下一个区间嵌套以前的一个区间的情况(因为前面的值至少有一个小于后面的值,所以肯定前面的值不可能是后面值的倍数),所以在遇到在一个集合里面,可以直接退出循环,不需要在向前枚举,可以使枚举变为线性。
代码
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
#define int long long
const int N = 2e5 + 10;
int p[N],id[N];
int a[N];
int find(int x){
if(x != p[x]) p[x] = find(p[x]);
return p[x];
}
bool cmp(int x,int y){
return a[x] < a[y];
}
signed main(){
int T;
cin >> T;
while(T--){
int n,k;
scanf("%lld%lld",&n,&k);
for(int i = 1;i <= n;i++){
scanf("%lld",&a[i]);
p[i] = id[i] = i;
}
sort(id + 1,id + 1 + n,cmp);
int ans = 0;
for(int i = 1;i <= n;i++){
int u = id[i];
int v = a[u];
int x = u - 1;
if(v >= k) break;
while(x > 0){
if(a[x] % v == 0){
int pa = find(x),pb = find(u);
if(pa != pb) p[pa] = pb,ans += v;
else break;
}
else break;
x--;
}
x = u + 1;
while(x <= n){
if(a[x] % v == 0){
int pa = find(x),pb = find(u);
if(pa != pb) p[pa] = pb,ans += v;
else break;
}
else break;
x++;
}
}
for(int i = 2;i <= n;i++){
int pa = find(i - 1),pb = find(i);
if(pa != pb) p[pa] = pb,ans += k;
}
printf("%lld\n",ans);
}
return 0;
}