数据结构二

复健Day5

数据结构二

3.并查集

(1)​亲戚

https://www.luogu.com.cn/problem/P1551

并查集模板

#include<iostream>
#include<cstdio>
#include<algorithm>
#define maxn 5010
using namespace std;

int fa[maxn];

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

void merge(int x,int y)
{
	int fx=find(x),fy=find(y);
	if(fx!=fy) fa[fx]=fy;
}

int main()
{
	int n,m,p;
	cin>>n>>m>>p;
	for(int i=1;i<=n;i++) fa[i]=i;
	for(int i=1;i<=m;i++)
	{
		int a,b;
		cin>>a>>b;
		merge(a,b);
	}
	while(p--)
	{
		int a,b;
		cin>>a>>b;
		if(find(a)!=find(b)) printf("No\n");
		else printf("Yes\n");
	}
	return 0;
}

(2)星球大战

https://www.luogu.com.cn/problem/P1197

每次打击一个星球之后,相当于删除了一条边,然后求此时的连通块数量。

求连通块数量且还有合并的操作(以太隧道),我们可以很快想到并查集的做法,但是并查集是加边,那么怎么样我们可以将其运用到这道题中呢,因为打击实在同一图中进行的,我们可以把这些打击操作存储下来,然后在从最后一次打击之后的状态开始往前回溯,这样就把删边转化为了加边了

下面这个代码完全就是模拟上述算法,最后是T了两个点

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<set>
#define maxn 400010
using namespace std;

int fa[maxn];
int x[maxn],y[maxn];
int injury[maxn];
int ans[maxn];
set<int> s;
set<int> match[maxn];
bool f[maxn];

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

void merge(int x,int y)
{
	int fx=find(x),fy=find(y);
	if(fx!=fy) fa[fx]=fy;
}

int main()
{
	int n,m,k;
	cin>>n>>m;
	for(int i=0;i<n;i++) fa[i]=i;
	for(int i=1;i<=m;i++)
	{
		cin>>x[i]>>y[i];
		match[x[i]].insert(y[i]);
		match[y[i]].insert(x[i]);
	}
	cin>>k;
	for(int i=1;i<=k;i++)
	{
		cin>>injury[i];
		s.insert(injury[i]);
	}
	//for(set<int>::iterator it=s.begin();it!=s.end();it++) printf("s:%d\n",*it);
	for(int i=1;i<=m;i++)
	{
		if(!s.count(x[i])&&!s.count(y[i])) merge(x[i],y[i]);
	}
	for(int i=0;i<n;i++)
	{
		int t=find(i);
		if(!f[t]&&!s.count(t))
		{
			//printf("t:%d\n",t);
			ans[k]++;
			f[t]=true;
		}
	}
	for(int i=k;i>=1;i--)
	{
		ans[i-1]=ans[i]+1;
		s.erase(injury[i]);
		for(set<int>::iterator it=match[injury[i]].begin();it!=match[injury[i]].end();it++)
		{
			if(!s.count(*it))
			{
				if(find(*it)!=find(injury[i]))
				{
					merge(*it,injury[i]);
					ans[i-1]--;
				}
			}
		}
	}
	for(int i=0;i<=k;i++) printf("%d\n",ans[i]);
	return 0;
}

上面的程序T掉的主要原因在于set的效率本身不算太高,改成快读之后就可以过了

下面这个程序是通过链式前向星的链表方式存储边的

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#define maxn 400010
using namespace std;

struct Edge
{
	int from,to,nxt;
	Edge(){}
	Edge(int from,int to,int nxt):from(from),to(to),nxt(nxt){}
}ed[maxn<<1];

int head[maxn],tot;

void add(int u,int v)
{
	ed[tot]=Edge(u,v,head[u]);
	head[u]=tot++;
	ed[tot]=Edge(v,u,head[v]);
	head[v]=tot++;
}

int fa[maxn];

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

bool out[maxn<<1];//该点是否被摧毁
int ans[maxn<<1],des[maxn<<1];

int main()
{
	int n,m,k;
	cin>>n>>m;
	memset(head,-1,sizeof(head));
	tot=1;
	for(int i=1;i<=n;i++) fa[i]=i;
	for(int i=1;i<=m;i++)
	{
		int x,y;
		cin>>x>>y;
		add(x,y);
	}
	cin>>k;
	int Ans=n-k;//一开始每一条边都没有加入且有k个点已被摧毁,故有这么多个连通块
	for(int i=1;i<=k;i++)
	{
		cin>>des[i];
		out[des[i]]=true;
	}
	for(int i=1;i<=(m<<1);i++)
	{
		int u=ed[i].from,v=ed[i].to;
		if(!out[u]&&!out[v])
		{
			int fu=find(u),fv=find(v);
			if(fu!=fv)
			{
				Ans--;
				fa[fu]=fv;
			}
		}
	}
	ans[k]=Ans;
	for(int i=k;i>=1;i--)
	{
		int x=des[i];
		Ans++;//因为修复了一个一个点
		out[x]=false;
		for(int j=head[x];~j;j=ed[j].nxt)
		{
			int u=ed[j].to;
			if(!out[u]&&find(u)!=find(x))
			{
				Ans--;
				fa[find(u)]=find(x);
			}
		}
		ans[i-1]=Ans;
	}
	for(int i=0;i<=k;i++) printf("%d\n",ans[i]);
	return 0;
}

(3)关押罪犯

https://www.luogu.com.cn/problem/P1525

由于我们要使最大的怨恨值最小,我们把怨恨值按从大到小排序,使得优先级更高(怨恨值更大)的一对罪犯不会出现在同一监狱中

我们每遇到一个点对,若与二者已经在同一集合中了(并查集查询),那么这一对之间的怨恨值就是最终的答案,我们跳出循环;而若不在一个点对中,我们就要将其放在两个不同的监狱中,可是a点放监狱1和放监狱2可能会造成不同的后果,比如说a和监狱1中的c点也具有怨恨关系,并且这个怨恨值比此时这个点对的怨恨值更大,若我们将其放监狱1会破坏最终的答案,而如何避免这种情况呢?我们遵循“敌人的敌人就是朋友”这一理论,每进行一次点对更新,就把这对点所在集合的根的敌人设为对方点所在集合的根就可以了,这样相互之间存在怨恨关系的点对就一定不会出现在同一集合中,若遍历到两点在同一集合中,那么此时就得到了答案

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#define maxn 200010
using namespace std;

struct Node
{
	int u,v,w,nxt;
	Node(){}
	Node(int u,int v,int w,int nxt):u(u),v(v),w(w),nxt(nxt){}
	bool operator < (const Node &rhs) const{
		return w>rhs.w;
	}
}node[maxn];

int head[maxn],tot;

void add(int u,int v,int w)
{
	node[tot]=Node(u,v,w,head[u]);
	head[u]=tot++;
	node[tot]=Node(v,u,w,head[v]);
	head[v]=tot++;
}

int fa[maxn];
int enemy[maxn];

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

void merge(int x,int y)
{
	int fx=find(x),fy=find(y);
	fa[fx]=fy;
}

int main()
{
	int n,m;
	memset(head,-1,sizeof(head));
	tot=1;
	cin>>n>>m;
	for(int i=1;i<=n;i++) fa[i]=i;
	for(int i=1;i<=m;i++)
	{
		int u,v,w;
		cin>>u>>v>>w;
		add(u,v,w);
	}
	int ans=0;
	sort(node+1,node+2*m+1);
	for(int i=1;i<=2*m+1;i++)
	{
		int u=node[i].u,v=node[i].v,w=node[i].w;
		int fu=find(u),fv=find(v);
		if(fu!=fv)
		{
			if(enemy[fv]) merge(u,enemy[fv]);
			if(enemy[fu]) merge(v,enemy[fu]);
			enemy[fv]=fu;
			enemy[fu]=fv;
		}
		else
		{
			ans=w;
			break;
		}
	}
	printf("%d\n",ans);
	return 0;
}

posted on   dolires  阅读(3)  评论(0编辑  收藏  举报

相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现

导航

< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5
点击右上角即可分享
微信分享提示