NOI 2001/POJ 1182 食物链

边带权并查集/扩展域并查集例题
【题意】
动物王国中有三类动物A,B,C,这三类动物的食物链构成了有趣的环形。
A吃B, B吃C,C吃A。
现有N个动物,以1-N编号。
每个动物都是A,B,C中的一种,但是我们并不知道它到底是哪一种。
有人用两种说法对这N个动物所构成的食物链关系进行描述:
第一种说法是”1 X Y”,表示X和Y是同类。
第二种说法是”2 X Y”,表示X吃Y。
此人对N个动物,用上述两种说法,一句接一句地说出K句话,这K句话有的是真的,有的是假的。
当一句话满足下列三条之一时,这句话就是假话,否则就是真话。
1) 当前的话与前面的某些真的话冲突,就是假话;
2) 当前的话中X或Y比N大,就是假话;
3) 当前的话表示X吃X,就是假话。
你的任务是根据给定的N和K句话,输出假话的总数。

边带权并查集

我们把环展开,可以发现深度与类别有一定的规律。
在这里插入图片描述
这里有向边x>yx->y,表示xyx吃y。根的深度为0。
d[x]d[x]表示xx的深度。则有:

  1. d[x]mod  3=0,xd[x] mod 3 =0,x与根是同类。
  2. d[x]mod  3=1,xd[x] mod 3 =1,x吃根。
  3. d[x]mod  3=2,xd[x] mod 3 =2,根吃x。

所以在判定假话时,x,yx,y在同一集合中的话。则有:

  1. (d[x]d[y])mod  3=0,xy(d[x]-d[y])mod 3 =0,x与y是同类
  2. (d[x]d[y])mod  3=1,xy(d[x]-d[y]) mod 3 =1,x吃y
  3. (d[x]d[y])mod  3=2,yx(d[x]-d[y]) mod 3 =2,y吃x

这里再讲一下连接两个集合时需要注意的问题。
DD表示x,yx,y的关系(读入后DD--),lenlen表示xyx的根与y的根连边的长度.
(d[x]+lend[y])D(mod  3)(d[x]+len-d[y])equiv D ( mod 3)
lenD(d[x]d[y])(mod  3)lenequiv D-(d[x]-d[y]) ( mod 3)

代码:

//边带权 
#include<cstdio>
#include<cctype>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#define g getchar()
using namespace std;
typedef long long ll;
const int N=50010;
template<class o>void qr(o&x) {
	char c=g;x=0;int f=1;
	while(!isdigit(c)){if(c=='-')f=-1;c=g;}
	while(isdigit(c))x=x*10+c-'0',c=g;
	x*=f;
}
void write(int x) {
	if(x/10)write(x/10);
	putchar(x%10+'0');
}

int n,m,fa[N],d[N],cnt;
int findfa(int x) {
	if( fa[x] == x ) return x;
	int root = findfa(fa[x]);
	d[x] += d[fa[x]];
	return fa[x] = root;
}
int main() {
	qr(n); qr(m); for(int i = 1;i<=n;i++) fa[i] = i;
	while(m--) {
		int D, x, y; qr(D); D--; qr(x); qr(y);
		if( x > n || y > n || ( D == 1 && x == y ) ) { ++cnt; continue; }
		int tx = findfa(x),ty = findfa(y);
		d[x] = ( d[x] % 3 + 3) % 3;
		d[y] = ( d[y] % 3 + 3) % 3;
		int dis = ( (d[x] - d[y]) + 3 ) % 3;
		//dis = 1表示x吃y,dis = 2表示y吃x,dis = 0表示x,y是同类 
		if( tx == ty ) {
			if( D != dis ) 
				{ ++cnt; continue; } 
		}
		else fa[tx] = ty,d[tx] = ( ( D - ( d[x] - d[y] ) ) % 3 + 3 ) % 3;
	}
	write(cnt);puts("");return 0;
}

扩展域并查集:

给每个点开三个点,表示同类、天敌、食物。

#include<cstdio>
#include<cctype>
#include<cstring>
#include<algorithm>
#define g getchar()
using namespace std;
typedef long long ll;
const int N=15e4+10;
template<class o>void qr(o&x) {
	char c=g;x=0;int f=1;
	while(!isdigit(c)){if(c=='-')f=-1;c=g;}
	while(isdigit(c))x=x*10+c-'0',c=g;
	x*=f;
}
void write(int x) {
	if(x/10)write(x/10);
	putchar(x%10+'0');
}

int fa[N],n,m,ans;
int findfa(int x) { return fa[x]==x?x:fa[x]=findfa(fa[x]);}
int main() {
	qr(n);qr(m);for(int i=1;i<=3*n;i++)fa[i]=i;
	while(m--) {
		int d,x,y;qr(d);qr(x);qr(y);
		if(x>n||y>n||(d==2&&x==y)){ans++;continue;}
		if(d==1) {//捕食、同类、天敌 
			if(findfa(x)==findfa(y+n)||findfa(x)==findfa(y+2*n)) 
				{ans++;continue;}
			fa[findfa(x)]=findfa(y);
			fa[findfa(x+n)]=findfa(y+n);
			fa[findfa(x+2*n)]=findfa(y+2*n);
		}
		else {
			if(findfa(x)==findfa(y)||findfa(x+n)==findfa(y)) 
				{ans++;continue;}
			fa[findfa(x)]=findfa(y+n);
			fa[findfa(x+n)]=findfa(y+2*n);
			fa[findfa(x+2*n)]=findfa(y);
		}
	}
	write(ans);puts("");return 0;
}

总结:

边带权并查集更加灵活,而扩展域并查集更加直观。

原文地址:https://www.cnblogs.com/zsyzlzy/p/12373882.html