题目链接
题目大意:有一串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; }