D. GCD and MST(最小生成树 + gcd)

Problem - D - Codeforces

 给定一个数组a,包含n (n > 2)个正整数和一个整数p。考虑一个无向加权图,有n个顶点,从1到n编号,其中顶点i和j (i < j)之间的边以以下方式添加:如果gcd(ai, ai+1, ai+2,, aj) = min(ai, ai+1, ai+2,, aj),则存在一条权值为min(ai, ai+1, ai+2,, aj)的边etwn和如果i + 1 = j,那么在i和j之间有一条权值为p的边。这里gcd(x, y,…)表示整数z, y, ....的最大公约数(gcd)请注意,如果上述两个条件都成立,i和j之间可能有多条边,如果i和j都不成立,那么这些顶点之间就没有边。目标是求出这个图的最小生成树的权值。输入第一行包含一个整数t (1 <t < 104)——测试用例的数量。每个测试用例的第一行包含两个整数n (2 <n<2-105)和p (1 < p < 10°)-节点的数量和参数p。第二行包含n个整数a1, a2, a3, ...., an (1 < ai < 10°)。它保证所有测试用例的n和不超过2 - 105。输出输出行。对于每个测试用例,打印对应图的权重。

Example

input

Copy

4
2 5
10 10
2 5
3 3
4 5
5 2 4 9
8 8
5 3 3 6 10 100 9 15

output

Copy

5
3
12
46

题解:
自己想着想着题意就想偏了,哎

按题意所说

其中顶点i和j (i < j)之间的边以以下方式添加:如果gcd(ai, ai+1, ai+2,, aj) = min(ai, ai+1, ai+2,, aj),则存在一条权值为min(ai, ai+1, ai+2,, aj)的边

假设区间最小值坐标为i,同时这个点也是gcd

这个区间在i两边的点是可以任意连边的,权值都是ai,既然如此我们可以让两边的点都连i,方便我们连边

从左到右记录左边最小gcd的位置,

从右往左也遍历一次,记录右边

#include<iostream>
#include<string>
#include<vector>
#include<cstring>
#include<algorithm>
#include<queue>
#include<cmath>
#include<map>
using namespace std;
typedef long long ll;
typedef pair<int,int> PII;
ll a[200050];
int l[200050];
int r[200050];
int f[200050];
struct node
{
	ll l,r,w;
	friend bool operator<(const node a,const node b)
	{
		return  a.w < b.w;
	}
};
int find(int x)
{
	if(f[x] == x)
	return f[x];
	return f[x] = find(f[x]);
}
void solve()
{
	ll n,k;
	scanf("%lld%lld",&n,&k);
	vector<node> p;
	for(int i = 1;i <= n;i++)
	{
		scanf("%lld",&a[i]);
		f[i] = i;
		if(i != 1)
		{
			p.push_back({i,i-1,k});
		}
	}
	l[1] = 1,r[n] = n;
	for(int i = 2;i <= n;i++)
	{
		if(a[l[i-1]] <= a[i] && a[i]%a[l[i-1]] == 0)
		{
			l[i] = l[i-1];
		}
		else
		{
			l[i] = i;
		}
	}
	for(int i = n - 1;i >= 1;i--)
	{
		if(a[r[i + 1]] <= a[i] && a[i]%a[r[i + 1]] == 0)
		{
			r[i] = r[i + 1];
		}
		else
		{
			r[i] = i;
		}
	}
	for(int i = 1;i <= n;i++)
	{
		if(l[i] < i)
		p.push_back({l[i],i,a[l[i]]});
		if(r[i] > i)
		p.push_back({r[i],i,a[r[i]]});
	}
	sort(p.begin(),p.end());
	int cnt = 0;
	ll ans = 0;
	for(node t:p)
	{
		int x = find(t.l);
		int y = find(t.r);
		if(x != y)
		{
			cnt++;
			ans += t.w;
			f[x] = y;
		}
		if(cnt == n - 1)
		break;
	}
	printf("%lld\n",ans);
}
signed main() 
{
//	ios::sync_with_stdio(0);
//	cin.tie(0);cout.tie(0);
	int t = 1;
    scanf("%d",&t);
	while (t--) 
	{
		solve();
	}
	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值