扩展域并查集&边带权并查集

[noi2001]食物链

嗯哼,noi里的绿题...

首先,这道题要用并查集应该很容易看出来,因为有"x和y是同类","x吃y"这样的句子,我一开始想的方法是搞三个并查集(好家伙),后来经过尝试无果,因为出现了(xxx,xx,xxxx,yy,y,yyyy)这些我取的不太友好的名字,所以为了易读,就只搞一个并查集,但是我们要开3n的空间:

[1,n]存同类的动物(self)

[n+1,2n]存要吃的动物(eat)

[2n+1,3n]存天敌(enemy),存的原因是"敌人的朋友是敌人","敌人的敌人是朋友"

我们继续分析题目,在合并之前,我们要分析"当前的话与前面的某些话是否冲突":

  • 情况1(同类),是假话的可能有,x吃y||y吃x
  • 情况2(吃),是假话的可能有,y吃x||x与y是同类.

接着考虑如果是真话怎么合并,同类的情况很好写,既然是同类那什么都一样就可以了,但是吃的情况要考虑一下,因为题目中说了A 吃 B,B 吃 C,C 吃 A,所以:

  1. x吃y
  2. y吃z
  3. 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;
}

[NOIP2010 提高组] 关押罪犯

很迷惑

我第一次提交的乱搞代码只得了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值,就可以利用路径压缩过程来统计每个节点到树根之间的路径信息

这题我们可以维护三个数组

  1. fa数组,存祖先
  2. d数组,存节点到祖先的距离
  3. 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;
}

posted @   Miraii  阅读(155)  评论(1编辑  收藏  举报
编辑推荐:
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示
主题色彩