并查集的妙用

\(\cal{A}.\)最简单的并查集

【图论_并查集】 [Luogu p1551] 亲戚

并查集最简单的思路,使用了其“并”与“查”的功能。

Luogu P3367 【模板】并查集

并查集裸题;

[Luogu 2078] 朋友

“并”“查”+计数

可以在每次合并时,以编号小的作为“父亲”,编号大的作为儿子,最后取小明和小红里“儿子”较小的一个

\(code:\)

#include<bits/stdc++.h>

using namespace std;

inline int read() {
	int ans=0;
	char last=' ',ch=getchar();
	while(ch>'9'||ch<'0') last=ch,ch=getchar();
	while(ch>='0'&&ch<='9') ans=(ans<<1)+(ans<<3)+ch-'0',ch=getchar();
	if(last=='-') ans=-ans;
	return ans;
}

int n,m,p,q,ans;
int fa[20050],cnt[20050];


int find(int x) {
	if(fa[x]!=x) fa[x]=find(fa[x]);
	return fa[x]; 
}

int main() {
	n=read();m=read();p=read();q=read();
	for(int i=1;i<=m+n;i++)
		fa[i]=i,cnt[i]=1;
	int x,y,fx,fy;
	for(int i=1;i<=p;i++) {
		x=read();
		y=read();
		fx=find(x);
		fy=find(y);
		if(fx!=fy) {
			int Fx=min(fx,fy);
			int Fy=max(fx,fy);
			fa[Fy]=Fx;
			cnt[Fx]+=cnt[Fy];
		}
	}
	for(int i=1;i<=q;i++) {
		x=read();
		y=read();
		x=-x; x+=n;
		y=-y; y+=n;
		fx=find(x); fy=find(y);
		if(fx!=fy) {
			int Fx=min(fx,fy);
			int Fy=max(fx,fy);
			fa[Fy]=Fx;
			cnt[Fx]+=cnt[Fy];
		}		
	}
	ans=min(cnt[1],cnt[1+n]);
	printf("%d",ans);
	return 0;
}

Luogu P1536 村村通

输入两个村庄后就把它们连起来,输入完毕后用i从1循环到n,所以如果i的父亲为它本身的话(它是祖先,它没有父亲),ans+1。答案要减1,因为三个点中只需用两条线连接,无需用三条线连接。(原blog)

\(\cal{B}.\)带权并查集

Luogu P1196NOI2002 银河英雄传说

\(code :\)

#include<bits/stdc++.h>

using namespace std;

inline int read() {
	int ans=0;
	char last=' ',ch=getchar();
	while(ch>'9'||ch<'0') last=ch,ch=getchar();
	while(ch>='0'&&ch<='9') ans=(ans<<1)+(ans<<3)+ch-'0',ch=getchar();
	if(last=='-') ans=-ans;
	return ans;
}

inline char Getchar() {
	char a;
	do {
		a=getchar();
	}while(a!='M'&&a!='C');
	return a;
}

struct node {
	int head,dist,cnt;
}boat[30010];

int T;
int fa[30010];

int find(int x) {
	if(fa[x]==x) return x;
	int k=fa[x];
	fa[x]=find(fa[x]);
	boat[x].dist+=boat[k].dist;
	boat[x].cnt=boat[k].cnt;
	return fa[x];
}

void Union(int A,int B) {
	int x=find(A),y=find(B);
	fa[x]=y;
	boat[x].dist+=boat[y].cnt;
	boat[y].cnt+=boat[x].cnt;
	boat[x].cnt=boat[y].cnt;
}

int main() {
	T=read();
	char c;
	int a,b;
	for(int i=1;i<=30000;i++) {
		boat[i].head=i;
		boat[i].dist=0;
		boat[i].cnt=1;
		fa[i]=i;
	}
	while(T--) {
		c=Getchar();
		a=read();
		b=read();
		if(c=='M') {
			Union(a,b);
		}
		else {
			int x=find(a);
			int y=find(b); 
			if(x!=y)
				printf("-1\n");
			else 
				printf("%d\n",abs(boat[a].dist-boat[b].dist)-1);
		}
	}
	return 0;
}

\(\cal{C}.\)最小生成树

[Luogu 2820] 局域网

要使除去的\(\sum f(i,j)\)最大,反向思考,就是使剩下的边的\(\sum f(i,j)\)最小,然后用\(Sum-\sum \limits^{rest} f(i,j)\)即为答案;

可以想到最小生成树

\(code:\)

#include<bits/stdc++.h>

using namespace std;

inline int read() {
	int ans=0;
	char last=' ',ch=getchar();
	while(ch>'9'||ch<'0') last=ch,ch=getchar();
	while(ch>='0'&&ch<='9') ans=(ans<<1)+(ans<<3)+ch-'0',ch=getchar();
	if(last=='-') ans=-ans;
	return ans;
}

int n,k,ans,m,num,tre;
int fa[20050];
struct node {
	int u,v,w;
}e[3000];

int find(int x) {
	if(fa[x]!=x) fa[x]=find(fa[x]);
	return fa[x]; 
}

bool cmp(node x,node y) {
	return x.w<y.w;
}

int main() {
	n=read();k=read();

	for(int i=1;i<=n;i++)
		fa[i]=i;

	for(int i=1;i<=k;i++) {
		e[i].u=read();
		e[i].v=read();
		e[i].w=read();
		num+=e[i].w;
	}

	sort(e+1,e+k+1,cmp);
	
	for(int i=1,u,v,w;i<=k;i++) {
		if(m>=n) break;
		u=e[i].u;
		v=e[i].v;
		w=e[i].w;
		int fu=find(u);
		int fv=find(v);
		if(fu!=fv) {
			fa[fu]=fv;
			tre+=w;
			m++;
		}
	}

	ans=num-tre;
	printf("%d",ans);
	return 0;
}

Luogu P1547 [USACO05MAR] Out fo Hay S

最小生成树\(Kruskal\),取最小生成树中最长的边

\(code:\)

#include<cstdio>
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<queue>
#include<cmath>
#include<string>
#define pa pair<int,int>
#define inf 2147483647

using namespace std;

inline int read(){
	int ans=0;
	char last=' ',ch=getchar();
	while(ch>'9'||ch<'0') last=ch,ch=getchar();
	while(ch>='0'&&ch<='9') ans=(ans<<1)+(ans<<3)+ch-'0',ch=getchar();
	if(last=='-') ans=-ans;
	return ans;
}

int n,m,maxx,cnt;
long long ans;
int fa[2010];
struct node{
	int x,y,l;
}g[10010];

int find(int x){
	if(fa[x]!=x) fa[x]=find(fa[x]);
	return fa[x]; 
}

bool cmp(node a,node b){
	return a.l<b.l;
}

int main(){
	n=read();m=read();
	for(int i=1;i<=m;i++){
		g[i].x=read();
		g[i].y=read();
		g[i].l=read();
	}
	sort(g+1,g+m+1,cmp);
	for(int i=1;i<=n;i++) fa[i]=i;
	int fv,fu;
	for(int i=1;i<=m&&cnt<=n-1;i++){
		fv=find(g[i].x);fu=find(g[i].y);
		if(fv==fu) continue;
		fa[fu]=fv;
		cnt++;
		maxx=max(maxx,g[i].l);
	}
	printf("%d",maxx);
	return 0;
}

[Luogu P1396] 营救

\(Kruskal\)最小生成树

\(s\)\(t\)已经相连了,停止并查集,输出此时的最大拥挤度

#include<bits/stdc++.h>

using namespace std;

inline int read() {
	int ans=0;
	char last=' ',ch=getchar();
	while(ch>'9'||ch<'0') last=ch,ch=getchar();
	while(ch>='0'&&ch<='9') ans=(ans<<1)+(ans<<3)+ch-'0',ch=getchar();
	if(last=='-') ans=-ans;
	return ans;
}

int n,m,s,t,ans;
struct node {
	int u,v,w;
}e[40010];
int fa[10010];

bool cmp(node x,node y) {
	return x.w<y.w;
}

int find(int x) {
	if(fa[x]!=x) fa[x]=find(fa[x]);
	return fa[x]; 
}

int main() {
	n=read(); m=read();
	s=read(); t=read();
	for(int i=1;i<=m;i++) {
		e[i].u=read();
		e[i].v=read();
		e[i].w=read();
	}
	for(int i=1;i<=n;i++)
		fa[i]=i;
	sort(e+1,e+m+1,cmp);
	int k=0;
	for(int i=1,u,v,w;i<=m;i++) {
		if(k>=n) break;
		u=e[i].u;v=e[i].v;w=e[i].w;
		int fu=find(u),fv=find(v);
		if(fu!=fv) {
			ans=max(ans,w);
			fa[fu]=fv;
			k++;
		}
		
		if(find(s)==find(t))
			break;
	}
	printf("%d",ans);
	return 0;
}

Luogu P1550 [USACO08OCT]Watering Hole G

新建一个水源节点,与所有田相连,权值为\(W_i\)

最小生成树\(Kruskal\)

#include<bits/stdc++.h>

using namespace std;

inline int read() {
	int ans=0;
	char last=' ',ch=getchar();
	while(ch>'9'||ch<'0') last=ch,ch=getchar();
	while(ch>='0'&&ch<='9') ans=(ans<<1)+(ans<<3)+ch-'0',ch=getchar();
	if(last=='-') ans=-ans;
	return ans;
}

int n,ecnt,ans;
struct node {
	int u,v,w;
}e[100110];
int fa[310],w[310];

bool cmp(node x,node y) {
	return x.w<y.w;
}

int find(int x) {
	if(fa[x]!=x) fa[x]=find(fa[x]);
	return fa[x]; 
}

int main() {
	n=read();
	for(int i=1,w;i<=n;i++) {
		w=read();
		e[++ecnt].u=i;
		e[ecnt].v=n+1;
		e[ecnt].w=w;
	}
	for(int i=1;i<=n;i++)
		for(int j=1,a;j<=n;j++) {
			a=read();
			if(i!=j) {
				++ecnt;
				e[ecnt].u=i;
				e[ecnt].v=j;
				e[ecnt].w=a;
			}
		}
	for(int i=1;i<=n;i++)
		fa[i]=i;
	sort(e+1,e+ecnt+1,cmp);
	int k=0;
	for(int i=1,u,v,w;i<=ecnt;i++) {
		if(k>=n) break;
		u=e[i].u;v=e[i].v;w=e[i].w;
		int fu=find(u),fv=find(v);
		if(fu!=fv) {
			ans+=w;
			fa[fu]=fv;
			k++;
		}
	}
	printf("%d",ans);
	return 0;
}

[Luogu P1111] 修复公路

最小生成树板子??

最终判断是否所有点都在生成树内(\(cnt==n-1?\)),输出最小生成树\(max\)

#include<cstdio>
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<queue>
#include<cmath>
#include<string>
#define pa pair<int,int>
#define inf 2147483647

using namespace std;

inline int read(){
	int ans=0;
	char last=' ',ch=getchar();
	while(ch>'9'||ch<'0') last=ch,ch=getchar();
	while(ch>='0'&&ch<='9') ans=(ans<<1)+(ans<<3)+ch-'0',ch=getchar();
	if(last=='-') ans=-ans;
	return ans;
}

int n,m,maxx,cnt;
long long ans;
int fa[1010];
struct node{
	int x,y,l;
}g[100010];

int find(int x){
	if(fa[x]!=x) fa[x]=find(fa[x]);
	return fa[x]; 
}

bool cmp(node a,node b){
	return a.l<b.l;
}

int main(){
	n=read();m=read();
	for(int i=1;i<=m;i++){
		g[i].x=read();
		g[i].y=read();
		g[i].l=read();
	}
	sort(g+1,g+m+1,cmp);
	for(int i=1;i<=n;i++) fa[i]=i;
	int fv,fu;
	for(int i=1;i<=m&&cnt<=n-1;i++){
		fv=find(g[i].x);fu=find(g[i].y);
		if(fv==fu) continue;
		fa[fu]=fv;
		cnt++;
		maxx=max(maxx,g[i].l);
		if(cnt==n-1) {
			printf("%d",maxx);
			return 0;
		}
	}
	if(cnt==n-1){
		printf("%d",maxx);
		return 0;
	}
	else printf("-1");
	return 0;
}

Luogu P1546 最短网络 Agri-Net

最小生成树裸题?\(Kruskal\)

#include<bits/stdc++.h>

using namespace std;


inline int read(){
	int ans=0;
	char last=' ',ch=getchar();
	while(ch>'9'||ch<'0') last=ch,ch=getchar();
	while(ch>='0'&&ch<='9') ans=(ans<<1)+(ans<<3)+ch-'0',ch=getchar();
	if(last=='-') ans=-ans;
	return ans;
}

struct edge{
    int from,to,dis;
}g[500001];
bool cmp(edge a,edge b){
    return a.dis<b.dis;
}
int fa[200001];
int find_father(int a){
    if(fa[a]==a)return a;
    fa[a]=find_father(fa[a]);
    return fa[a];
}
int n,m,cnt,x;
long long ans;
int main(){
    n=read();
    for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			x=read();
			if(x&&i<j) g[++m].from=i,g[m].to=j,g[m].dis=x;
		}
	}
    for(int i=1;i<=n;i++) fa[i]=i;
    sort(g+1,g+m+1,cmp);
    for(int i=1;i<=m&&cnt<=n-1;i++){
        int fu=find_father(g[i].from),fv=find_father(g[i].to);
        if(fu==fv)continue;
        fa[fu]=fv;
        cnt++;
        ans+=g[i].dis;
    }
    if(cnt==n-1){
        cout<<ans<<endl;
    }else{
        cout<<"No Solution.\n";
    }
}

\(\cal{D}.\)脑洞并查集

\(\frak {a}.\)奇怪二维并查集

【一本通1347】格子游戏

\(\frak {b}.\)奇怪脑洞

Luogu p2456 二进制方程

Luogu P1197 [JSOI2008]星球大战

反过来考虑,一个一个星球往上加

把所有询问离线下来,倒着做。

一个整理

posted @ 2020-04-04 11:55  Sweetness  阅读(355)  评论(0编辑  收藏  举报