扩展域并查集&边带权并查集
嗯哼,noi里的绿题...
首先,这道题要用并查集应该很容易看出来,因为有"x和y是同类","x吃y"这样的句子,我一开始想的方法是搞三个并查集(好家伙),后来经过尝试无果,因为出现了(xxx,xx,xxxx,yy,y,yyyy)这些我取的不太友好的名字,所以为了易读,就只搞一个并查集,但是我们要开的空间:
存同类的动物(self)
存要吃的动物(eat)
存天敌(enemy),存的原因是"敌人的朋友是敌人","敌人的敌人是朋友"
我们继续分析题目,在合并之前,我们要分析"当前的话与前面的某些话是否冲突":
- 情况1(同类),是假话的可能有,x吃y||y吃x
- 情况2(吃),是假话的可能有,y吃x||x与y是同类.
接着考虑如果是真话怎么合并,同类的情况很好写,既然是同类那什么都一样就可以了,但是吃的情况要考虑一下,因为题目中说了A 吃 B,B 吃 C,C 吃 A,所以:
- x吃y
- y吃z
- z吃x
代码实现
#include <iostream>
#include <cstdio>
using namespace std;
int ans,fa[500004];
int n,k;
int read(){
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-') f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<3)+(x<<1)+ch-'0';ch=getchar();}
return x*f;
}
inline int find(int x){
if(fa[x]==x) return x;
return fa[x]=find(fa[x]);
}
int main(){
n=read();k=read();
for(int i=1;i<=n*3;i++){
fa[i]=i;
}
for(int i=1;i<=k;i++)
{
int c,x,y;
c=read();x=read();y=read();
if(x>n||y>n) {ans++;continue;}
if(c==1){
int x1=find(x),y1=find(y);
int x2=find(x+n),y2=find(y+n);
int x3=find(x+(n<<1)),y3=find(y+(n<<1));
if(x2==y1||x1==y2){ans++;continue;}
fa[x1]=y1,fa[x2]=y2,fa[x3]=y3;
}
else{
if(x==y){ans++;continue;}
int x1=find(x),y1=find(y);
int x2=find(x+n),y2=find(y+n);
int x3=find(x+(n<<1)),y3=find(y+(n<<1));
if(y2==x1||x1==y1){ans++;continue;}
fa[x2]=y1,fa[x1]=y3,fa[x3]=y2;
}
}
cout<<ans<<endl;
return 0;
}
很迷惑
我第一次提交的乱搞代码只得了10分(可是直接输出0有20分!hhh,我一开始也是想维护两个并查集,
这个思路是对的,但是不知道怎么实现,因为我维护了两个真正意义上的A,B监狱,这样就会出现一个问题
如果两个人都还没被分进监狱,谁进A,谁进B?哈哈,于是我就想--直接乱搞,前面一个进A,后面一个进B,于是只得了10分...
这题满足了镜像思想,所以我们可以不用管到底这个罪犯进哪一个监狱,所以我们可以这样做
- 对于一个罪犯x,用x表示它的进的监狱,用x+n表示他的补集,也就是他不在的集合
这样,如果我们可以把两个罪犯分开来,然后x,y互进补集
代码
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
int read(){
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-') f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<3)+(x<<1)+ch-'0';ch=getchar();}
return x*f;
}
struct node{
int a,b,c;
}cri[102053];
int fa[100000],n,m;
inline bool cmp(node a,node b){
return a.c>b.c;
}
inline int find(int x){
if(fa[x]==x) return x;
return fa[x]=find(fa[x]);
}
int main(){
n=read();m=read();
for(int i=1;i<=m;i++){
cri[i].a=read();cri[i].b=read();cri[i].c=read();
}
for(int i=1;i<=2*n;i++)
fa[i]=i;
sort(cri+1,cri+m+1,cmp);//对冲突值排序,贪心,反着来看什么时候不能拆开来
for(int i=1;i<=m;i++)
{
int x1=find(cri[i].a),y1=find(cri[i].b);
int x2=find(cri[i].a+n),y2=find(cri[i].b+n);
if(x1==y1) {cout<<cri[i].c<<endl;return 0;}//因为是镜像的,所以只要满足了一个,那另一个也一定满足
fa[x1]=y2,fa[x2]=y1;
}
cout<<0<<endl;
return 0;
}
[边带权并查集]#
直接上例题:NOI2002 银河英雄传说
这里引用《算法竞赛进阶指南》的一段话:
并查集实际上是由若干棵树构成的森林,我们可以在树中的每条边上记录一个权值,即维护一个数组d,用d[i]保存节点i到父节点fa[i]之间的边权
在每次路径压缩后,每个访问过的节点都会直接指向树根,如果我们同时更新这些节点的d值,就可以利用路径压缩过程来统计每个节点到树根之间的路径信息
这题我们可以维护三个数组
- fa数组,存祖先
- d数组,存节点到祖先的距离
- size,存节点所在子树的长度,这个要初始化为1
然后就是路径压缩时维护d数组的代码
inline int find(int x) {
if(fa[x]==x) return x;
int root=find(fa[x]); //先将find(fa[x])存放在root中,否则会出错
d[x]+=d[fa[x]];
return fa[x]=root;
}
代码实现
#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
using namespace std;
int size[30004],fa[30045],t;
int d[30005];
int find(int x){
if(fa[x]==x) return x;
int root=find(fa[x]);
d[x]+=d[fa[x]];
return fa[x]=root;
}
int main(){
cin>>t;
for(int i=1;i<=30002;i++){
fa[i]=i;
size[i]=1;//队列的长度初始化为1
}
for(int i=1;i<=t;i++){
char c;int x,y;
cin>>c;cin>>x>>y;
if(c=='M'){
int xx=find(x),yy=find(y);
fa[xx]=yy;//合并
d[xx]+=size[yy];//x要合并到y的队尾
size[yy]+=size[xx];
}
else{
int xx=find(x),yy=find(y);
if(xx==yy) cout<<abs(d[x]-d[y])-1<<endl;
else cout<<-1<<endl;
}
}
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现