ybtoj 并查集 躲避拥挤 题解

题目链接 躲避拥挤https://www.ybtoj.com.cn/contest/148/problem/6https://www.ybtoj.com.cn/contest/148/problem/6

选择某些边形成连通块,若一个连通块的节点为 n ,则这个连通块中符合要求的点对 (a,b) 为 n(n-1),连通块可由并查集维护。当两个集合合并时,对于点对数的新贡献有 (k_{1}+k_{2})(k_{1}+k_{2}-1)-(k_{1}(k_{1}-1)+k_{2}(k_{2}-1))=2k_{1}k_{2}。对于每一个询问跑一遍并查集时间复杂度为 O(Tqm) 显然超时。我们发现若可以走的边 w ≤ x1 ,那么在 w ≤ x2 且 x1 ≤ x2 时那些边仍然可以走,原来的联通性并不会改变,那么对所有边按边权小到大进行排序,对询问可走的边权小到大排序,一边选边一边回答询问,结束后一起输出,即离线做法,时间复杂度为O(T (n logn+mlogm))

Code:

#include<stdio.h>
#include<algorithm>
using namespace std;
int t;
int n,m,q,ans;
int fa[20005],k[20005];//fa为并查集数组,k记录集合个数
struct E{int u,v,w;}e[100005];
struct Q{int x,bh,ans;}que[100005];
bool cmp1(E x,E y){return x.w<y.w;};//按边权小到大排序
bool cmp2(Q x,Q y){return x.x<y.x;};//按询问可走边权的小到大排序
bool cmp3(Q x,Q y){return x.bh<y.bh;};//按输入编号排序
int F(int x){
	if(fa[x]==x)return x;
	else return fa[x]=F(fa[x]);
}
int main(){
	scanf("%d",&t);
	while(t--){
        ans=0;
		scanf("%d%d%d",&n,&m,&q);
		for(int i=1;i<=n;i++)fa[i]=i,k[i]=1;
		for(int i=1;i<=m;i++)scanf("%d%d%d",&e[i].u,&e[i].v,&e[i].w);
		for(int i=1;i<=q;i++)scanf("%d",&que[i].x),que[i].bh=i;
		sort(e+1,e+m+1,cmp1);
		sort(que+1,que+q+1,cmp2);
		int j=1;//表示选到第j条边
		for(int i=1;i<=q;i++){
			while(e[j].w<=que[i].x && j<=m){//边权要不大于询问的x值
				if(F(e[j].u)!=F(e[j].v)){//若已经联通则无视
					int fu=F(e[j].u),fv=F(e[j].v);
					fa[fu]=fv,ans+=2*k[fv]*k[fu];//合并集合,更新点对
					k[fv]+=k[fu],k[fu]=0;//集合个数更新
				}
				j++;
			}
				
			que[i].ans =ans;//回答问题	
		}
		sort(que+1,que+q+1,cmp3);
		for(int i=1;i<=q;i++)printf("%d\n",que[i].ans);
	}
	return 0;
}

Guess you like

Origin blog.csdn.net/yiyi049/article/details/121714994