码迷,mamicode.com
首页 > 其他好文 > 详细

【LsWn的数据结构】并查集

时间:2020-04-28 17:25:46      阅读:53      评论:0      收藏:0      [点我收藏+]

标签:com   压缩   http   一个开始   unit   tac   其他   training   opera   

(题目参考:BF数据结构题单

普通并查集

代码实现

普通并查集支持 \(2\) 种操作 —— 查询自己在哪个连通块和合并两个联通块(即连边)

操作 1:查询

技术图片

对于我们一个点,查询的连通块记为 \(id_u\),一个连通块的编号为这个连通块中被所有人指向的那个节点。

对于一次查询,我们向上找自己指向的边,然后一步一步这样爬上去。

对于查询,我们可以直接记录下这个节点的 \(id_u\),查找时 \(id_u=\operatorname{find}(id_u)\),这样即路径压缩,可以把复杂度降到 \(O(logn)\)

如果一个节点的 \(id\) 就是自己,意味着这个点就是被指向的节点。

一开始初始化 \(id_u=u\)

int find(int u){return id[u]==u?u:id[u]=find(id[u]);}

操作 2:合并

若要对 \(2\)\(4\) 进行连边,即合并连通块 \(3\)\(5\),我们直接让 \(5\) 指向 \(3\) (或者 \(3\) 指向 \(5\)) 即可。

void unite(int u,int v){id[find(u)]=find(v);}

题目

[JSOI2008] 星球大战

由于目前了解的并查集还不支持删除操作,而且更加不能在短时间内算出有多少连通块,我们考虑离线操作。

如果两个点合并,那么连通块数就会 \(-1\),所以我们倒叙操作,先合并所有不在询问中的边,然后从最后一个点开始,合并所有和他有边的连通块。

最后存储答案输出即可。

#include<bits/stdc++.h>
using namespace std;
const int N=4e5+9;
struct edge{int u,v;}e[N]; int id[N]; int c[N],tot,ans[N]; bool ap[N];
int find(int i){return i==id[i]?i:id[i]=find(id[i]);}
void unite(int u,int v){id[find(u)]=find(v);}
vector<int>ve[N];
int main(){
	int n,m,k; scanf("%d%d",&n,&m); tot=n;
	for(int i=1;i<=m;i++) scanf("%d%d",&e[i].u,&e[i].v),e[i].u++,e[i].v++;
	for(int i=1;i<=n;i++) id[i]=i;
	scanf("%d",&k); for(int i=1;i<=k;i++) scanf("%d",&c[i]),c[i]++,ap[c[i]]=1;
	for(int i=1;i<=m;i++){
		if((!ap[e[i].u])&&(!ap[e[i].v]))
			if(find(e[i].u)!=find(e[i].v)) tot--,unite(e[i].u,e[i].v);
		ve[e[i].u].push_back(e[i].v),ve[e[i].v].push_back(e[i].u);
	}tot-=k;
	for(int i=k;i>=1;i--){
		ans[i]=tot; tot++; ap[c[i]]=0;
		for(int j=0;j<ve[c[i]].size();j++){
			if(find(ve[c[i]][j])!=find(c[i])&&!ap[ve[c[i]][j]]) tot--,unite(ve[c[i]][j],c[i]);
		}
	}
	cout<<tot<<endl;
	for(int i=1;i<=k;i++) cout<<ans[i]<<endl;
	return 0;
}

海滩防御

假设最左边一列为 \(s\),最右边一列为 \(t\),那么对于包括 \(s\)\(t\) 的所有 \(n+2\) 个点进行连边。注意两个防御塔的连边应该是欧几李德距离除以 \(2\)

然后采用贪心的思想对边用 \(w\) 排序,不停加边直到 \(s\)\(t\) 连通。

#include<bits/stdc++.h>
using namespace std;
const int N=1000009;
struct edge{int u,v;double w;}e[N*2]; int tot;
bool cmp(const edge&a,const edge&b){
	return a.w<b.w;
}
struct node{int x,y;}a[N];
double dis(int x0,int y0,int x2,int y2){return sqrt((x0-x2)*(x0-x2)+(y0-y2)*(y0-y2));}
int s,t,id[N];
int find(int i){return id[i]==i?i:id[i]=find(id[i]);}
void unite(int u,int v){id[find(u)]=find(v);}
double ans=0;
int main(){
	int n,m;scanf("%d%d",&m,&n);
	for(int i=1;i<=n+1;i++) id[i]=i;
	for(int i=1;i<=n;i++) scanf("%d%d",&a[i].y,&a[i].x);
	for(int i=1;i<n;i++) for(int j=i+1;j<=n;j++)
		e[++tot]=(edge){i,j,sqrt((a[i].x-a[j].x)*(a[i].x-a[j].x)+(a[i].y-a[j].y)*(a[i].y-a[j].y))/2};
	for(int i=1;i<=n;i++)
		e[++tot]=(edge){i,0,a[i].y},e[++tot]=(edge){i,n+1,m-a[i].y};
	sort(e+1,e+tot+1,cmp); int ans=0;;
	for(int i=1;find(0)!=find(n+1);i++){
		if(find(e[i].u)!=find(e[i].v)) id[find(e[i].u)]=find(e[i].v);
		ans=i;
	}
	printf("%.2lf",e[ans].w);
	return 0;
}

[SCOI2010] 连续攻击

这题挺巧妙的。

对于一个攻击,连接两个属性。然后会产生连通块。对于一个连通块,我们从第一个开始,就要选择自己的边。如果有 \(sz-1\) 条边,那么最大的不能被选。否则全都能选。最后处理能选的答案即可。

#include<bits/stdc++.h>
using namespace std;
const int N=2e6+9;
int id[N],mx[N],ed[N],nod[N],x[N],y[N],vst[N];
int find(int i){return i==id[i]?i:id[i]=find(id[i]);}
void unite(int u,int v){id[find(u)]=find(v);}
int main(){
	for(int i=1;i<=10000;i++) id[i]=i;
	int T; cin>>T; int p=T;
	while(T--){
		scanf("%d%d",&x[T+1],&y[T+1]); unite(x[T+1],y[T+1]);
	}
	for(int i=1;i<=p;i++) ed[id[x[i]]]++,vst[x[i]]=vst[y[i]]=1;
	for(int i=1;i<=10000;i++) mx[id[i]]=max(mx[id[i]],i),nod[id[i]]++;
	for(int i=1;i<=10000;i++) if(ed[id[i]]<nod[id[i]]) vst[mx[id[i]]]=0;
	int ans=0; vst[0]=1;
	for(;;ans++) if(!vst[ans]) break;
	printf("%d\n",ans-1);
	return 0;
}

[NOI2001] 食物链

有三个集合,\(A,B,C\),然后要存储吃/被吃关系的话我们需要拆点。一个点拆成 \(3\) 个点,放入这 \(3\) 个集合,分别是“这个集合的主人”,“吃这个集合里的主人的动物”,“被这个集合里的主人吃的动物”。然后进行存储。如果任意两个自己拆的点在同一连通块那么肯定是 NO,还有很多离谱的情况,判断即可。

#include<bits/stdc++.h>
using namespace std;
const int N=3e5+9;
int id[N]; 
int find(int i){return id[i]==i?i:id[i]=find(id[i]);}
void unite(int i,int j){id[find(i)]=id[find(j)];}
int n,m,ans;
void init(){for(int i=1;i<=n*3;i++) id[i]=i;}
int main(){
	scanf("%d%d",&n,&m); init();
	for(int i=1,t,u,v;i<=m;i++){
		scanf("%d%d%d",&t,&u,&v); if(u>n||v>n){ans++;continue;}
		find(u),find(u+n),find(u+2*n),find(v),find(v+n),find(v+n*2);
		if(t==1){
			if(id[u+n]==id[v]||id[u+2*n]==id[v]) ans++;
			else unite(u,v),unite(u+n,v+n),unite(u+2*n,v+2*n);
		}else{
			if(find(u)==find(v)) ans++;
			else if(id[u+2*n]==id[v]) ans++; 
			else unite(u+n,v),unite(u+2*n,v+n),unite(u,v+2*n);
		}
	}
	return printf("%d",ans),0;
}

带权并查集

支持操作:并查集的操作,节点到根节点的距离,子树大小等。

代码实现

find 函数路径更新时,要顺便更新自己维护的其他值。

int d[N],s[N]; //到根的距离&块的深度 
int find(int u){
	if(id[u]==u) return u;
	int tmp=id[u]; //!
	id[u]=find(id[u]);
	d[u]+=d[tmp];  //!
	s[u]=s[id[u]];
	return id[u];
}

unite 要顺便更新一下带的标记。注意,d 数组更新需要就事论事。具体可以见下面的题目。

void unite(int u,int v){
	int idu=find(u),idv=find(v); if(idu==idv) return;
	id[idu]=idv;
	//d数组更新 
	s[idv]=(s[idu]+=s[idv]);
} 

题目

[NOI2002]银河英雄传说

根节点到总根节点的距离即前面有多少战舰,也就是前面联通块的大小。

#include<bits/stdc++.h>
using namespace std;
const int N=30009;
int id[N],d[N],s[N];
int find(int u){
	if(id[u]==u) return u;
	int tmp=id[u];
	id[u]=find(id[u]);
	d[u]+=d[tmp]; 
	s[u]=s[id[u]];
	return id[u];
}
void unite(int u,int v){
	int idu=find(u),idv=find(v); if(idu==idv) return;
	id[idu]=idv;
	d[idu]=s[idv];
	s[idv]=(s[idu]+=s[idv]);
} 
int main(){
	int T; cin>>T;
	for(int i=1;i<=30000;i++) id[i]=i,s[i]=1;
	while(T--){
		char c; int u,v; cin>>c>>u>>v;
		if(c==‘M‘) unite(u,v);
		else printf("%d\n",find(u)!=find(v)?-1:abs(d[u]-d[v])-1);
	}
	return 0;
} 

[USACO04OPEN]Cube Stacking G

这题和上一题差不多,唯一的地方是查询需要改一下。

#include<bits/stdc++.h>
using namespace std;
const int N=100009;
int id[N],d[N],s[N];
int find(int u){
	if(id[u]==u) return u;
	int tmp=id[u];
	id[u]=find(id[u]);
	d[u]+=d[tmp]; 
	s[u]=s[id[u]];
	return id[u];
}
void unite(int u,int v){
	int idu=find(u),idv=find(v); if(idu==idv) return;
	id[idu]=idv;
	d[idu]=s[idv];
	s[idv]=(s[idu]+=s[idv]);
} 
int main(){
	int T; cin>>T;
	for(int i=1;i<=100000;i++) id[i]=i,s[i]=1;
	while(T--){
		char c; int u,v; cin>>c>>u;
		if(c==‘M‘) cin>>v,unite(u,v);
		else id[u]=find(u),printf("%d\n",d[u]);
	}
	return 0;
} 

【LsWn的数据结构】并查集

标签:com   压缩   http   一个开始   unit   tac   其他   training   opera   

原文地址:https://www.cnblogs.com/TetrisCandy/p/12795069.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!