边带权并查集/扩展域并查集例题
【题意】
动物王国中有三类动物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句话,输出假话的总数。
边带权并查集
我们把环展开,可以发现深度与类别有一定的规律。
这里有向边,表示。根的深度为0。
设表示的深度。则有:
所以在判定假话时,在同一集合中的话。则有:
- 。
- 。
- 。
这里再讲一下连接两个集合时需要注意的问题。
设表示的关系(读入后),表示.
代码:
//边带权
#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;
}
总结:
边带权并查集更加灵活,而扩展域并查集更加直观。