2023NOIP A层联测6 万花筒

文章讨论了如何在一张有n个点和m条边的无向带权图中,通过观察其在万花筒中的变换规则,求解生成树的权值和。利用Kruskal算法和连通块的概念,通过计算每个边集的贡献,得出最小生成树的总权值。时间复杂度为O(m)。

题目大意

有一张有nnn个点mmm条边的无向带权图GGG,小艾将它放到一个万花筒中观察。在万花筒中,对于每条边(u,v,w)∈G(u,v,w)\in G(u,v,w)G,会在HHH中生成全体((u+1) mod +1,(v+i) mod n+1,w)((u+1)\bmod+1,(v+i)\bmod n+1,w)((u+1)mod+1,(v+i)modn+1,w)的边,最后的图HHH就是在万花筒中看到的图。

求这个图HHH的最小生成树的权值和,保证生成树存在。

TTT组数据。

1≤T≤100,1≤n,w≤109,∑m≤1051\leq T\leq 100,1\leq n,w\leq 10^9,\sum m\leq 10^51T100,1n,w109,m105


题解

对于GGG中的每一条边(u,v,w)(u,v,w)(u,v,w),令k=(u−v+n)%nk=(u-v+n)\% nk=(uv+n)%n,则在HHH中,任意两个满足(i−j+n)%n=k(i-j+n)\% n=k(ij+n)%n=k的两个点i,ji,ji,j都有一条权值为www的边。那么,对于GGG中的每一条边(u,v,w)(u,v,w)(u,v,w),我们记其对应的HHH中的边集为T(k,w)T(k,w)T(k,w)

利用Kruskal\text{Kruskal}Kruskal算法的思想,我们将这些边集按www从小到大排序,并依次遍历以构建最小生成树。

设当前的nnn个点中没有任何两个点有连边,当前枚举到的边集为T(k,w)T(k,w)T(k,w),则将nnn中所有满足(i−j+n)%n=k(i-j+n)\% n=k(ij+n)%n=k且不在同一个连通块的两个点i,ji,ji,j连一条边。那么,这nnn个点就会分为d=gcd⁡(n,k)d=\gcd(n,k)d=gcd(n,k)个连通块,且同一个连通块中的点的编号模ddd后的值一定相等(可以自己举几个例子试一下)。

这样的话,这个图就连上了n−dn-dnd条边,边集T(k,w)T(k,w)T(k,w)对答案的贡献为(n−d)×w(n-d)\times w(nd)×w

再考虑下一组边集T(k′,w′)T(k',w')T(k,w),类似地,将nnn中所有满足(i−j+n)%n=k′(i-j+n)\% n=k'(ij+n)%n=k且不在同一个连通块的两个点i,ji,ji,j连一条边。这时,我们发现,iii所在的连通块中的点模ddd后的值均为i%di\%di%djjj所在的连通块中的点模ddd后的值均为j%dj\% dj%d,那么我们可以将iiijjj连边看作i%di\% di%dj%dj\% dj%d连边(i%di\% di%dj%dj\% dj%d的值为000时将其值看作ddd),这样显然是等价的。

换句话说,我们将每个连通块都看成了一个点,而这个点表示的连通块就是一棵树。

那么,在连完T(k,w)T(k,w)T(k,w)的边之后,问题可以看作剩下ddd个点且其中没有任何两个点有连边,要求其在连上剩下的边集后的最小生成树的权值和。

每次多加一个边集,就按上面所说的操作一次,并算上边的贡献。因为保证生成树存在,所以最终一定只剩下一个点,而这一个点所表示的连通块就是图HHH的最小生成树。

将每次操作的贡献求和,即可得到答案。

时间复杂度为O(m)O(m)O(m)

code

#include<bits/stdc++.h>
using namespace std;
int T,n,m,now,d;
long long ans;
struct node{
	int t,w;
}v[100005];
bool cmp(node ax,node bx){
	return ax.w<bx.w;
}
int gcd(int i,int j){
	while(j){
		i%=j;swap(i,j);
	}
	return i;
}
int main()
{
	freopen("kaleidoscope.in","r",stdin);
	freopen("kaleidoscope.out","w",stdout);
	scanf("%d",&T);
	while(T--){
		scanf("%d%d",&n,&m);
		for(int i=1,x,y,z;i<=m;i++){
			scanf("%d%d%d",&x,&y,&z);
			v[i].t=(x-y+n)%n;
			v[i].w=z;
		}
		sort(v+1,v+m+1,cmp);
		now=n;
		ans=0;
		for(int i=1;i<=m;i++){
			d=gcd(now,v[i].t%now);
			ans+=1ll*(now-d)*v[i].w;
			now=d;
		}
		printf("%lld\n",ans);
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值