Parity game
边带权并查集与扩展域并查集。
首先我们有一个基本想法,那就是把区间和的奇偶性转变为两个前缀和的奇偶关系。
简单来说,设 $c(x)$ 为这个序列的前缀和。
如果说 $c(r)-c(l-1)$ 为偶数,则 $c(r)$ 和 $c(l-1)$ 是奇偶性相同的。
如果说 $c(r)-c(l-1)$ 为奇数,则 $c(r)$ 与 $c(l-1)$ 是奇偶性不同的。
那么离散化询问后,我们考虑使用并查集解决问题。
先说边带权。这里设 0 为与其奇偶性相同,1 为与其奇偶性不同。
那么边权一路异或到树根就是 $x$ 与树根的奇偶性关系。
每次合并之前,我们先用 Get
检查 $x$ 和 $y$ 是否在同一个集合内(顺便得到 $d(x)$ 与 $d(y)$,即两者分别与树根的奇偶性关系),如果是的话,$d(x)\operatorname{xor} d(y)$ 即为 $x$ 与 $y$ 的奇偶关系。那么此时如果输入给出的奇偶关系与其不符的话,这个时刻就是答案;反之,跳过。
如果两者不在同一集合中,我们就要考虑合并。
假设 $u$ 是 $x$ 的祖先,$v$ 是 $y$ 的祖先,不妨将 $u$ 连为 $v$ 的子节点。
那么 $d(u)$ 即 $u$ 与 $v$ 的奇偶关系,由于我们给出的奇偶关系 $ans=d(x)\operatorname{xor} d(u)\operatorname{xor} d(y)$,移项就能得到 $d(u)=d(x)\operatorname{xor}d(y)\operatorname{xor} ans$。
于是我们就可以很好的维护整个并查集系统了。
代码(边带权并查集):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
| #include<iostream> #include<cstdio> #include<algorithm> #define ll long long using namespace std; namespace Ehnaev{ inline ll read() { ll ret=0,f=1;char ch=getchar(); while(ch<48||ch>57) {if(ch==45) f=-f;ch=getchar();} while(ch>=48&&ch<=57) {ret=(ret<<3)+(ret<<1)+ch-48;ch=getchar();} return ret*f; } inline void write(ll x) { static char buf[22];static ll len=-1; if(x>=0) {do{buf[++len]=x%10+48;x/=10;}while(x);} else {putchar(45);do{buf[++len]=-(x%10)+48;x/=10;}while(x);} while(len>=0) putchar(buf[len--]); } }using Ehnaev::read;using Ehnaev::write; inline void writeln(ll x){write(x);putchar(10);}
const ll N=1e6;
ll n,m,amt; ll fa[N+5],d[N+5],uq[N+5];
struct Qry{ll l,r,ans;}q[N+5];
inline ll Get(ll x) { if(x==fa[x]) return x;ll rt=Get(fa[x]);d[x]^=d[fa[x]];return fa[x]=rt; }
int main() {
n=read();m=read();
for(ll i=1;i<=m;i++) { q[i].l=read();q[i].r=read();char op[5];scanf("%s",op); if(op[0]=='o') q[i].ans=1;else q[i].ans=0; uq[++amt]=q[i].l-1;uq[++amt]=q[i].r; } sort(uq+1,uq+amt+1);amt=unique(uq+1,uq+amt+1)-uq-1; for(ll i=1;i<=amt;i++) fa[i]=i;
ll flg=0; for(ll i=1;i<=m;i++) { ll x,y; x=lower_bound(uq+1,uq+amt+1,q[i].l-1)-uq; y=lower_bound(uq+1,uq+amt+1,q[i].r)-uq; ll u=Get(x),v=Get(y); if(u==v) { if((d[x]^d[y])!=q[i].ans) {writeln(i-1);flg=1;break;} } else {fa[u]=v;d[u]=d[x]^d[y]^q[i].ans;} } if(!flg) writeln(m);
return 0; }
|
我们还有另一种思路,就是扩展域并查集。
把每个点 $x$ 拆成两个点 $x_{odd}$ 和 $x_{even}$,分别表示 $x$ 是奇数和 $x$ 是偶数这两个命题。
那么连边变成了条件可以相互推出,那么我们的思路就简单很多,只要判断与给出条件矛盾的命题在并查集系统中是否成立,就能够得到答案。
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
| #include<iostream> #include<cstdio> #include<algorithm> #define ll long long using namespace std; namespace Ehnaev{ inline ll read() { ll ret=0,f=1;char ch=getchar(); while(ch<48||ch>57) {if(ch==45) f=-f;ch=getchar();} while(ch>=48&&ch<=57) {ret=(ret<<3)+(ret<<1)+ch-48;ch=getchar();} return ret*f; } inline void write(ll x) { static char buf[22];static ll len=-1; if(x>=0) {do{buf[++len]=x%10+48;x/=10;}while(x);} else {putchar(45);do{buf[++len]=-(x%10)+48;x/=10;}while(x);} while(len>=0) putchar(buf[len--]); } }using Ehnaev::read;using Ehnaev::write; inline void writeln(ll x){write(x);putchar(10);}
const ll N=1e6;
ll n,m,amt; ll fa[N+5],uq[N+5];
struct Qry{ll l,r,ans;}q[N+5];
inline ll Get(ll x) { if(x==fa[x]) return x;return fa[x]=Get(fa[x]); }
int main() {
n=read();m=read();
for(ll i=1;i<=m;i++) { q[i].l=read();q[i].r=read();char op[5];scanf("%s",op); if(op[0]=='o') q[i].ans=1;else q[i].ans=0; uq[++amt]=q[i].l-1;uq[++amt]=q[i].r; } sort(uq+1,uq+amt+1);amt=unique(uq+1,uq+amt+1)-uq-1; for(ll i=1;i<=amt*2;i++) fa[i]=i;
ll flg=0; for(ll i=1;i<=m;i++) { ll u,v; u=lower_bound(uq+1,uq+amt+1,q[i].l-1)-uq; v=lower_bound(uq+1,uq+amt+1,q[i].r)-uq; ll u_odd=u,u_even=u+amt; ll v_odd=v,v_even=v+amt; if(q[i].ans==0) { if(Get(u_odd)==Get(v_even)) { writeln(i-1);flg=1;break; } fa[Get(u_odd)]=Get(v_odd);fa[Get(u_even)]=Get(v_even); } else { if(Get(u_odd)==Get(v_odd)) { writeln(i-1);flg=1;break; } fa[Get(u_odd)]=Get(v_even);fa[Get(u_even)]=Get(v_odd); } } if(!flg) writeln(m);
return 0; }
|