数据结构二
复健
数据结构二
并查集
亲戚
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;
}
星球大战
https://www.luogu.com.cn/problem/P1197
每次打击一个星球之后,相当于删除了一条边,然后求此时的连通块数量。
求连通块数量且还有合并的操作(以太隧道),我们可以很快想到并查集的做法,但是并查集是加边,那么怎么样我们可以将其运用到这道题中呢,因为打击实在同一图中进行的,我们可以把这些打击操作存储下来,然后在从最后一次打击之后的状态开始往前回溯,这样就把删边转化为了加边了
下面这个代码完全就是模拟上述算法,最后是了两个点
#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;
}
上面的程序掉的主要原因在于的效率本身不算太高,改成快读之后就可以过了
下面这个程序是通过链式前向星的链表方式存储边的
#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;
}
关押罪犯
https://www.luogu.com.cn/problem/P1525
由于我们要使最大的怨恨值最小,我们把怨恨值按从大到小排序,使得优先级更高(怨恨值更大)的一对罪犯不会出现在同一监狱中
我们每遇到一个点对,若与二者已经在同一集合中了(并查集查询),那么这一对之间的怨恨值就是最终的答案,我们跳出循环;而若不在一个点对中,我们就要将其放在两个不同的监狱中,可是点放监狱和放监狱可能会造成不同的后果,比如说和监狱中的点也具有怨恨关系,并且这个怨恨值比此时这个点对的怨恨值更大,若我们将其放监狱会破坏最终的答案,而如何避免这种情况呢?我们遵循“敌人的敌人就是朋友”这一理论,每进行一次点对更新,就把这对点所在集合的根的敌人设为对方点所在集合的根就可以了,这样相互之间存在怨恨关系的点对就一定不会出现在同一集合中,若遍历到两点在同一集合中,那么此时就得到了答案
#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;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现