食物链&Parity game(并查集的两种妙用)

题目:【NOI2001】食物链

  • 扩展域并查集
  • 将每一个动物拆成三个点
  • 比如说x,拆成x,x+n,x+2n,分别对应x对应A,B,C三种动物
  • 并且在同一个集合里面的事件都发生或都不发生,比如x,y+n,在同一个集合里,就意味着,如果x是A,那么y就是B。
  • 接下来,我们考虑每一句话
  • 如果是同类关系,那么我们首先判断是不是会跟前面的冲突,即判断x与y是否存在捕食或被捕食的关系,就是看(x,y+n),(x,y+2n)这两对,只要有一对是在同一个集合里,那就是矛盾了。然后,我们将他们的对应项合并,(x,y),(x+n,y+n),(x+2n,y+2n)。
  • 如果是捕食关系,那就判一下,两者是否有同类或者x被y捕食的情况
  • 最后的合并也是类似的。
    code
#include<cstdio>
#include<algorithm>
#define fo(i,a,b) for (int(i)=(a);(i)<=(b);(i)++)
using namespace std;
const int N=50000+255;
int f1,f2,f[N*3],n,k,flag,x,y,ans;
int find(int x){
	if (f[x]==x) return x;
	else f[x]=find(f[x]);
	return f[x];
}
bool pd(int x,int y){
	return find(x)==find(y);
}
void work(int x,int y){
	f1=find(x); f2=find(y);
	if (f1!=f2) f[f1]=f2;
}
int main(){
	//freopen("a.in","r",stdin);
	//freopen("a.out","w",stdout);
	scanf("%d%d",&n,&k);
	fo(i,1,3*n) f[i]=i;
	while (k--){
		scanf("%d%d%d",&flag,&x,&y);
		if (x<1||x>n||y<1||y>n) {
				ans++; continue;
		}
		if (flag==1){
			if (pd(x,y+n)||pd(x,y+2*n)) ans++;
			else{
				work(x,y); work(x+n,y+n); work(x+2*n,y+2*n);
			}
		}
		else{
			if (pd(x,y+2*n)||pd(x,y)) ans++;
			else{
				work(x,y+n); work(x+n,y+2*n); work(x+2*n,y);
			}
		}
	}
	printf("%d",ans);
	return 0;
}
  • 题目:Parity game,
  • sum表示前缀和,如果是奇数个一,那么suml-1与sumr的奇偶性不同,否则相同。
  • 这道题的特别之处在于,如果x与y奇偶性不同,x与z奇偶性不同,则y与z奇偶性相同。
  • 那么我们在做并查集的时候,我们可以多记录一个东西,表示与它父亲的奇偶性是否相同。
  • 遇到一个区间时,我们首先判断,l-1和r是否在同一个集合中。
  • 如果是,则判断他们与当前给出的奇偶性是否矛盾,具体来说,我们将x到树根路径上的所有d值异或起来,就得到了跟树根的奇偶性是否相同,然后即可判断。
  • 如果不是,那么新连的边的d值,应该是多少,显然应该是d[x] xor d[y] xor ans
  • code
#include<cstdio> 
#include<algorithm>
#define fo(i,a,b) for (int (i)=(a);(i)<=(b);(i)++)
using namespace std;
const int N=10005;
int f[N],d[N],l[N],r[N],a[N*2],k,m,ans[N],t,n,x,y,f1,f2;
char s[10];
int find(int x){
	if (x==f[x]) return f[x];
	int root=find(f[x]); d[x]^=d[f[x]];
	return f[x]=root;
}
int main(){
	//freopen("a.in","r",stdin);
	//freopen("a.out","w",stdout);
	while (~~scanf("%d%d",&k,&m)==2){
		t=0;
		fo(i,1,m){
			scanf("%d%d%s",&l[i],&r[i],s);
			ans[i]=(s[0]=='o');
			a[++t]=l[i]-1; a[++t]=r[i];
		}
		sort(a+1,a+t+1);
		n=unique(a+1,a+t+1)-a-1;
		fo(i,1,n) f[i]=i,d[i]=0;
		int end=m;
		fo(i,1,m){
			x=lower_bound(a+1,a+n+1,l[i]-1)-a;
			y=lower_bound(a+1,a+n+1,r[i])-a;
			f1=find(x); f2=find(y);
			if (f1^f2){
				f[f2]=f1; d[f2]=d[x]^d[y]^ans[i];
			}
			else{
				if ((d[x]^d[y])!=ans[i]){
				end=i-1; break;
				}	
			}
		}
		printf("%d",end);
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值