题目链接
题目大意:有一串0,1组成的序列,每次A会询问B,l-r一段区间总含有1的个数是偶数还是奇数。B会回答是奇数还是偶数,但是B可能会撒谎。比如:B曾经回答过1-3中有奇数个1,4-6中有偶数个1,但是后面问B,l-r中有1的个数是奇数还是偶数,B回答是偶数,这与前面的回答相矛盾,所以B说谎了。题目要求出前1~k个数是正确的,求K是多少。
输入:输入N表示01串的长度,M表示的询问次数,接下来M行,每行输入两个数a,b和even/odd,
a,b表示区间的左右端点,even表示偶数,odd表示奇数。
输出:K.
1、带权并查集
总体思路:
我们可以用一个sum数组来记录前i个有偶数个1还是有奇数个1.
我们可以发现中间一段区间有偶数个1,那么sum[l-1]和sum[r]的奇偶行性相同。
如果中间一段区间有奇数个1,那么sum[l-1]和sum[r]的奇偶性不相同。
这两条就是我们用并查集来进行关系传递的条件。
接下来我们设数组D,初始值为0,d[x]=0表示以x+1节点为头节点,和区间的右端点的奇偶性相同,d[x]=1表示奇偶性不同。一个集合就表示一段连续的区间,将x,y合并到同一集就需要进行d[p]=ans(某种操作)d[x](某种操作)d[y],(d[p]表示x根节点和y节点之间的奇偶关系,ans表示x和y之间的关系)。合并是两个根节点之间的合并,根节点的d[i]都是0,所以d[p]=ans(某种操作)0(某种操作)0,当ans=1(表示区间1的个数是奇数)这是两个端点的奇偶不同,所以d[p]等于1,当ans=0(表示区间1的个数是偶数)这是两个端点的奇偶相同,所以d[p]等于0,这刚好满足异或运算,所以通过对树根d[x]的所有边权进行异或运算就可以得到x与根节点之间的奇偶关系。
以上图片方便理解d[p]=d[x]^d[y]^ans;
同时边权的传递关系也满足异或运算:
A、如果d[y]=1表示和根节点奇偶不同,1、d[x]=0表示x和y的奇偶一样,所以d[y](某种运算)d[x]=d[x]=1;2、d[x]=1表示x和y的奇偶不一样,所以d[y](某种运算)d[x]=d[x]=0.
B、如果d[y]=0表示和根节点奇偶相同,1、d[x]=0表示x和y的奇偶一样,所以d[y](某种运算)d[x]=d[x]=0;2、d[x]=1表示x和y的奇偶不一样,所以d[y](某种运算)d[x]=d[x]=1.
这都符合异或运算。
#include<bits/stdc++.h>
using namespace std;
int n,m;
struct ty{
int l;
int r;
string x;
int f;
};
ty t[10005];
int a[20010];
int pre[20010];
int d[20010];
int cnt=0;
/*int find(int x){
int r=x;
while(r!=pre[r]){
r=pre[r];
}
int j=x,temp;
这里更新d数组出现了错误,只更新了d[x],但是x到根节点的路径上其他节点的d值没有更新
while(pre[j]!=r){
temp=pre[j];
pre[j]=r;
j=temp;
d[x]=d[x]^d[j];
}
return r;
}*/
int find(int x){
if(x==pre[x]) return x;
int root=find(pre[x]);//递归计算集合代表
d[x]^=d[pre[x]];//维护d数组
return pre[x]=root;//路径压缩
}
void merge(int x,int y,int ans){
int xx=find(x);
int yy=find(y);
if(xx!=yy){
pre[xx]=yy;
d[xx]=d[x]^ans^d[y];
}
return ;
}
int main(){
for(int i=0;i<=20005;i++) pre[i]=i;
cin>>n>>m;
for(int i=1;i<=m;i++){
cin>>t[i].l>>t[i].r>>t[i].x;
if(t[i].x[0]=='o') t[i].f=1;
else t[i].f=0;
a[++cnt]=t[i].l-1;
a[++cnt]=t[i].r;
}
sort(a+1,a+cnt+1);
n=unique(a+1,a+cnt+1)-(a+1);//数据过大以上都是进行离散化
for(int i=1;i<=m;i++){
int left=lower_bound(a+1,a+n+1,t[i].l-1)-a;
int right=lower_bound(a+1,a+n+1,t[i].r)-a;
if(find(left)==find(right)){
if(d[left]^d[right]==t[i].f) continue;
//如果在一个集合里表示在一个区间里,d[left]^d[right]==t[i].f表示话为真继续,否则输出i-1;
else{
cout<<i-1<<endl;
return 0;
}
}
else{
merge(left,right,t[i].f);//进行区间合并
}
}
cout<<m<<endl;
return 0;
} 
京公网安备 11010502036488号