题目传送门
小 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 N≤109,M≤10000
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[l,r]有奇数个1时, s u m [ l − 1 ] sum[l-1] sum[l−1]与 s u m [ r ] sum[r] sum[r]奇偶性相同
当区间 S [ l , r ] S[l,r] S[l,r]有偶数个1时, s u m [ l − 1 ] sum[l-1] sum[l−1]与 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个节点
#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;
}
边带权的代码如下,感兴趣的读者可以自行思考一下,由于作者不擅长边带权,就不做过多讲解
#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;
}