《算法竞赛进阶指南》0x41 T4 Parity Game

题目传送门

题目描述

小 A 和小 B 在玩一个游戏。

首先,小 A 写了一个由 0 和 1 组成的序列 S,长度为 N。

然后,小 B 向小 A 提出了 M 个问题。

在每个问题中,小 B 指定两个数 l 和 r,小 A 回答 S[l∼r] 中有奇数个 1 还是偶数个 1。

机智的小 B 发现小 A 有可能在撒谎。

例如,小 A 曾经回答过 S[1∼3] 中有奇数个 1,S[4∼6] 中有偶数个 1,现在又回答 S[1∼6] 中有偶数个 1,显然这是自相矛盾的。

请你帮助小 B 检查这 M 个答案,并指出在至少多少个回答之后可以确定小 A 一定在撒谎。

即求出一个最小的 k,使得 01 序列 S 满足第 1∼k 个回答,但不满足第 1∼k+1 个回答。

输入格式

第一行包含一个整数 N,表示 01 序列长度。

第二行包含一个整数 M,表示问题数量。

接下来 M 行,每行包含一组问答:两个整数 l 和 r,以及回答 even 或 odd,用以描述 S[l∼r] 中有偶数个 1 还是奇数个 1。

输出格式

输出一个整数 k,表示 01 序列满足第 1∼k 个回答,但不满足第 1∼k+1 个回答,如果 01 序列满足所有回答,则输出问题总数量。

数据范围

N ≤ 1 0 9 , M ≤ 10000 N≤10^9,M≤10000 N109,M10000

输入样例:

10
5
1 2 even
3 4 odd
5 6 even
1 6 even
7 10 odd

输出样例:

3

题解

首先把这道题的并查集思想彻底体现出来
将题目中的询问转化一下
维护 s u m sum sum数组表示序列 S S S的前缀和
当区间 S [ l , r ] S[l,r] S[lr]有奇数个1时, s u m [ l − 1 ] sum[l-1] sum[l1] s u m [ r ] sum[r] sum[r]奇偶性相同
当区间 S [ l , r ] S[l,r] S[lr]有偶数个1时, s u m [ l − 1 ] sum[l-1] sum[l1] s u m [ r ] sum[r] sum[r]奇偶性不同
所以我们只需要维护并查集,判断在何时出现矛盾即可
第一步要进行离散化,例如程序自动分析中的,排序去重加二分,防止空间爆炸
并查集是一种可以动态维护具有传递性关系的数据结构
把每个变量x拆成两个节点 x o d d x_{odd} xodd x e v e n x_{even} xeven
其中 x o d d x_{odd} xodd表示 s u m [ x ] sum[x] sum[x]是奇数, x e v e n x_{even} xeven表示 s u m [ x ] sum[x] sum[x]是偶数
对于每个问题,我们设 l + 1 l+1 l+1 r r r离散化后的结果分别为 x , y x,y x,y,设 a n s ans ans表示该问题的回答
1.当 a n s = 0 ans=0 ans=0,合并 x o d d , y o d d x_{odd},y_{odd} xodd,yodd x e v e n , y e v e n x_{even},y_{even} xeven,yeven
2.当 a n s = 1 ans=1 ans=1,合并 x o d d , y e v e n x_{odd},y_{even} xodd,yeven x e v e n , y o d d x_{even},y_{odd} xeven,yodd
在执行合并操作前,我们也需要提前判断此时是否已经推出矛盾,如果是就直接输出即可
注意初始化时要初始化2*n个节点

code
#include
using namespace std;
const int N=20010;
int n,m;
struct node
{
	int l,r,type;
}a[N];
int t=0,b[N],fa[N],d[N];
int get(int x)
{
	if(x==fa[x]) return x;
	return fa[x]=get(fa[x]);
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++)
	{
		char str[5];
		scanf("%d%d%s",&a[i].l,&a[i].r,str);
		if(str[0]=='e') a[i].type=0;
		else a[i].type=1;
		b[++t]=a[i].l-1;
		b[++t]=a[i].r;
	}
	sort(b+1,b+1+t);
	n=unique(b+1,b+1+t)-(b+1);
	for(int i=1;i<=2*n;i++) fa[i]=i;
	for(int i=1;i<=m;i++)
	{
		int x=lower_bound(b+1,b+1+n,a[i].l-1)-b;
		int y=lower_bound(b+1,b+1+n,a[i].r)-b;
		int x_odd=x,x_even=x+n;
		int y_odd=y,y_even=y+n;
		if(a[i].type==1)
		{
			if(get(x_odd)==get(y_odd))
			{
				cout<<i-1;
				return 0;
			}
			fa[get(x_odd)]=get(y_even);
			fa[get(x_even)]=get(y_odd);
		}
		else 
		{
			if(get(x_odd)==get(y_even))
			{
				cout<<i-1;
				return 0;
			}
			fa[get(x_odd)]=get(y_odd);
			fa[get(x_even)]=get(y_even);
		}
	}
	cout<<m;
	return 0;
 } 

边带权的代码如下,感兴趣的读者可以自行思考一下,由于作者不擅长边带权,就不做过多讲解

code
#include
using namespace std;
const int N=20010;
int n,m;
struct node
{
	int l,r,type;
}a[N];
int t=0,b[N],fa[N],d[N];
int get(int x)
{
	if(x==fa[x]) return x;
	int root=get(fa[x]);
	d[x]^=d[fa[x]];
	return fa[x]=root;
}
int main()
{
	freopen("PARITY.in","r",stdin);
	freopen("PARITY.out","w",stdout);
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++)
	{
		char str[5];
		scanf("%d%d%s",&a[i].l,&a[i].r,str);
		if(str[0]=='e') a[i].type=0;
		else a[i].type=1;
		b[++t]=a[i].l-1;
		b[++t]=a[i].r;
	}
	sort(b+1,b+1+t);
	n=unique(b+1,b+1+t)-(b+1);
	for(int i=1;i<=n;i++) fa[i]=i;
	for(int i=1;i<=m;i++)
	{
		int x=lower_bound(b+1,b+1+n,a[i].l-1)-b;
		int y=lower_bound(b+1,b+1+n,a[i].r)-b;
		int p=get(x),q=get(y);
		if(p==q)
		{
			if((d[x]^d[y])!=a[i].type)
			{
				cout<<i-1;
				return 0;
			}
		}
		else 
		{
			fa[p]=q;
			d[p]=d[x]^d[y]^a[i].type;
		}
	}
	cout<<m;
	return 0;
 } 

你可能感兴趣的:(算法竞赛进阶指南,c++)