POJ 1733 Parity game

前言

并查集是一种维护可传递关系的有力数据结构!


分析

值域那么大,所以明显要用离散化。

s u m sum sum数组表示前缀1出现的次数。对于每个回答有两种情况:

  1. s [ l ∼ r ] s[l\sim r] s[lr]有偶数个1,则 2 ∣ s u m [ r ] − s u m [ l − 1 ] 2|sum[r]-sum[l-1] 2sum[r]sum[l1], s u m [ l − 1 ] , s u m [ r ] sum[l-1],sum[r] sum[l1],sum[r]奇偶性相同。
  2. s [ l ∼ r ] s[l\sim r] s[lr]有奇数个1,则 2 ∣ s u m [ r ] − s u m [ l − 1 ] + 1 2|sum[r]-sum[l-1]+1 2sum[r]sum[l1]+1, s u m [ l − 1 ] , s u m [ r ] sum[l-1],sum[r] sum[l1],sum[r]奇偶性不同。

因为奇偶性关系具有如下传递性:

  1. x 、 y x、y xy奇偶性相同, y 、 z y、z yz奇偶性相同,则 x 、 z x、z xz奇偶性相同。
  2. x 、 y x、y xy奇偶性相同, y 、 z y、z yz奇偶性不同,则 x 、 z x、z xz奇偶性不同。
  3. x 、 y x、y xy奇偶性不同, y 、 z y、z yz奇偶性不同,则 x 、 z x、z xz奇偶性相同。

方法1:

这样我们就可以边带权——用1表示两个变量不同,用0表示两个变量相同。
定义 d [ x ] d[x] d[x]表示x与并查集中父亲的奇偶性关系,就可以很轻松地跑了。

连接两个集合时,需要注意的是边权的长度。
a n s ans ans表示两个变量的奇偶性相同(1表示不同,0表示相同), x , y x,y x,y表示 l − 1 , r l-1,r l1,r的离散值, p , q p,q p,q x , y x,y x,y的祖先(并查集代表)。
a n s = d i s [ x ∼ p ] xor ⁡ d i s [ p ∼ q ] xor ⁡ d i s [ q ∼ y ] ( 这 里 的 d i s 是 不 同 与 d 的 ) ans=dis[x\sim p] \operatorname{xor}dis[p\sim q]\operatorname{xor} dis[q\sim y](这里的dis是不同与d的) ans=dis[xp]xordis[pq]xordis[qy](disd)
所以,若把 p 连 向 q p连向q pq,则 d [ p ] = d [ x ] xor ⁡ d [ y ] xor ⁡ a n s d[p]=d[x]\operatorname{xor} d[y]\operatorname{xor} ans d[p]=d[x]xord[y]xorans.

代码:

#include<cstdio>
#include<cctype>
#include<cstring>
#include<algorithm>
#define g getchar()
using namespace std;
typedef long long ll;
const int N=20010;
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,d[N],fa[N];
int findfa(int x) {
	if(fa[x]==x)return x;
	int root=findfa(fa[x]);
	d[x]^=d[fa[x]];
	return fa[x]=root;
}
struct rec {int l,r;bool ans;}q[N>>1];
int a[N],tot;
void disc() {
	qr(n);qr(m);tot=0;
	for(int i=1;i<=m;i++) {
		qr(q[i].l),qr(q[i].r);
		char s[7];scanf("%s",s);
		q[i].ans=(s[0]=='o');
		a[++tot]=q[i].l-1;a[++tot]=q[i].r;
	}
	sort(a+1,a+tot+1);
	tot=unique(a+1,a+tot+1)-(a+1);
}
int main() {
	disc();for(int i=1;i<=tot;i++)fa[i]=i;
	for(int i=1;i<=m;i++) {
		int x=lower_bound(a+1,a+tot+1,q[i].l-1)-a,
			y=lower_bound(a+1,a+tot+1,q[i].r)-a,
			ans=q[i].ans,tx,ty;
		tx=findfa(x);ty=findfa(y);
		if( tx == ty ) {
			if( (d[x]^d[y]) != q[i].ans )
				{write(i-1);puts("");return 0;}
		}
		else {
			fa[ty]=tx;
			d[ty]=d[x]^d[y]^ans;
		}
	}
	write(m);puts("");
	return 0;
}

总结:

边带权并查集其实就是用并查集记录无向图的边。


方法2:

我们把每个变量 ( s u m [ x ] ) (sum[x]) (sum[x])拆成两个点——x_odd,x_even(分别表示 s u m [ x ] sum[x] sum[x]为奇数的情况和 s u m [ x ] sum[x] sum[x]为偶数的情况.)
本质上是把点分成两个集合。
x , y x,y x,y l − 1 , r l-1,r l1,r的离散值.那么对于每个回答,分两种情况讨论:

  1. 奇偶性相同。若x_odd与y_even在同一个集合内,则矛盾。
    否则,连接(x_odd,y_odd),(x_even,y_even).

  2. 奇偶性不同。若x_odd与y_odd在同一个集合内,则矛盾。
    否则,连接(x_odd,y_even),(x_even,y_odd).

并查集维护的实际上是这样对称的关系。
在这里插入图片描述
这是一种更加直观的做法。

代码:

//扩展域——其实就是把一个点拆分成几个点。 
#include<cstdio>
#include<cctype>
#include<cstring>
#include<algorithm>
#define g getchar()
using namespace std;
typedef long long ll;
const int N=20010;
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],m;
int findfa(int x) { return fa[x] == x?x:fa[x] = findfa(fa[x]);}
struct node {int x,y;bool ans;}q[N>>1];
int a[N],tot;
void disc() {
	qr(m);qr(m);tot=0;char s[8];
	for(int i=1;i<=m;i++) {
		qr(q[i].x);qr(q[i].y);
		scanf("%s",s);
		q[i].ans = (s[0] == 'o');
		a[++tot]=q[i].x-1;
		a[++tot]=q[i].y;
	}
	sort(a+1,a+tot+1);
	tot=unique(a+1,a+tot+1)-(a+1);
}
int main() {
	disc();for(int i=1;i<=2*tot;i++)fa[i]=i;
	for(int i=1;i<=m;i++) {
		int x=lower_bound(a+1,a+tot+1,q[i].x-1)-a;
		int y=lower_bound(a+1,a+tot+1,q[i].y  )-a;
		int x_odd = x,x_even = x + tot;
		int y_odd = y,y_even = y + tot;
		if(!q[i].ans) {
			if(findfa(x_odd)==findfa(y_even)) {
				write(i-1);puts("");
				return 0;
			}
			fa[findfa(x_odd)]=findfa(y_odd);
			fa[findfa(x_even)]=findfa(y_even);
		}
		else {
			if(findfa(x_odd)==findfa(y_odd)) {
				write(i-1);puts("");
				return 0;
			}
			fa[findfa(x_odd)]=findfa(y_even);
			fa[findfa(y_odd)]=findfa(x_even);
		}
	}
	write(m);puts("");
	return 0;
}
	
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值