CF1439 E. Cheat and Win
在一个坐标系上,定义有效的点\((x,y)\)为满足\(x \and y=0\)的点。四相邻的点互相连边。
可以证明这是棵树,规定\((0,0)\)为根节点。
一开始有些点是黑点,有些点是白点。两人博弈,每次可以操作一个黑点,将它和它到祖先路径上的任意点集的颜色反转。最后不能操作的输。
树的初始状态由给出\(m\)条路径,将\(m\)条路径的并染黑决定。
后手想要作弊,他在博弈前做若干次操作,每次选择一个点,将其到根路径上所有点颜色反转。最小化操作次数使得后手赢。
\(m\le 10^5\)
\(x,y\in[0,10^9]\)
这是棵树的证明:可以发现\((x,y)\)和\((x-1,y),(x,y-1)\)中恰好一个点连边。
核心问题:博弈必胜的判定条件。
放结论:按照深度分层,统计每层的黑点个数,如果存在一层的黑点个数为奇数,那么先手必胜。
证明考虑,当满足这个条件的时候,先手可以用一次操作将每一层的黑点个数调成偶数。到了后手时,他无论怎么操作,操作之后肯定存在一层的黑点个数为奇数。又因为必败状态每层黑点个数为\(0\),所以一直做下去一定是后手必输。不满足这个条件的证明类似。
另外有个阴间的考虑方法:可以将原问题拆成若干个子问题,求形如\(f_x\)表示只有\(x\)染黑的\(sg\)值。
假设这样考虑正确,那么原问题\(sg\)为\(\ xor_{x\in Black} \ f_x\)。转移可以将相同深度的点看做是等价的状态,枚举点集转移过去,得到它们的异或和,再将每种转移的情况\(mex\)起来。归纳一下可以发现\(f_x=2^{dep_x}\)。最终和上面的那个结论是等价的。
接下来口胡一下为什么这样考虑是正确的。这里会用到我的一些乱搞的理论,缺乏理性证明。
现在换个问题:每次选择一个不为空的点集,将其反转。区别在于没有限制最下面那个点是黑点。如果最下面那个点是白点,操作过后后手可以重新调整会当前的局面,所以操作白点不会变得更优。
不过似乎这样调整过后黑点和白点也没什么区别;所以引入一个势函数:\(\phi(S)=xor_{x\in Black(S)} 2^{dep_x}\)。显然终止时\(\phi(S)=0\),并且它同时也是最小值。因为双方都希望获胜,不希望对方跳到比当前局面优的情况,
感性理解得操作的过程中势函数一直单调不增。这样操作黑点和白点的区别在于:操作黑点可以使势函数减少。
于是从这个角度不严谨地说明了两个问题是等价的。
后面的东西忽略上面乱讲的也没有关系。
现在改变了问题,于是不同的操作互不影响。
唯一要考虑的是原问题到了终止态的时候,如何和若干个子问题对应起对应关系呢?
原问题到终止态的时\(sg=0\),此时若干个子问题可能还没有结束,尽管如此它们异或和也为\(0\)。这说明了:如果继续做下去,那么先手必败。这就相当于是终止态了。
知道了这个结论之后,建个虚树然后随便搞一下即可。
求\(LCA\)可以\(O(\lg)\)求,比较\(dfn\)序也可以通过求\(LCA\)做。
具体就是如果不在同一条线段上,往父亲的方向跳一大步直到到了拐点为止。
using namespace std;
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <map>
#include <cassert>
#define N 400005
#define lowbit(x) ((x)&-(x))
#define mp(x,y) (make_pair(x,y))
int n,m;
struct DOT{
int x,y;
int dep(){return x+y;}
DOT fa(){
if (x==0 && y==0) return (DOT){0,-1};
if (x==0) return (DOT){x,y-1};
if (y==0) return (DOT){x-1,y};
return lowbit(x)<lowbit(y)?(DOT){x-1,y}:(DOT){x,y-1};
}
DOT top(){
if (x==0 || y==0) return (DOT){0,0};
if (lowbit(x)>lowbit(y)) return (DOT){x,y-(y&lowbit(x)-1)};
return (DOT){x-(x&lowbit(y)-1),y};
}
} d[N];
bool operator<(DOT a,DOT b){return a.x<b.x || a.x==b.x && a.y<b.y;}
map<DOT,int> id;
bool operator==(DOT a,DOT b){return a.x==b.x && a.y==b.y;}
bool same_line(DOT a,DOT b){return a.top()==b.top() && (a.x==b.x || a.y==b.y);}
DOT LCA(DOT a,DOT b){
while (!same_line(a,b))
if (a.top().dep()>b.top().dep())
a=a.top();
else
b=b.top();
return a.dep()<b.dep()?a:b;
}
bool cmpd(DOT a,DOT b){
DOT A=a,B=b;
while (!same_line(a,b))
if (a.top().dep()>b.top().dep())
a=a.top();
else
b=b.top();
DOT lca=a.dep()<b.dep()?a:b;
if (B==lca) return 0;
if (A==lca) return 1;
return a.y==b.y?a.x<b.x:a.y>b.y;
}
struct edge{DOT u,v;} ed[N];
struct EDGE{
int to;
EDGE *las;
} e[N];
int ne;
EDGE *last[N];
void link(int u,int v){
e[ne]={v,last[u]};
last[u]=e+ne++;
}
int build(){
static int st[N];
int tp=1;
st[1]=1;
int _m=m;
for (int i=2;i<=m;++i){
DOT _lca=LCA(d[i],d[st[tp]]);
if (!id[_lca]){
d[++_m]=_lca;
id[_lca]=_m;
}
int lca=id[_lca];
while (tp>1 && d[lca].dep()<d[st[tp-1]].dep()){
link(st[tp-1],st[tp]);
--tp;
}
if (tp && d[lca].dep()<d[st[tp]].dep()){
link(lca,st[tp]);
--tp;
if (!(lca==st[tp]))
st[++tp]=lca;
}
st[++tp]=i;
}
while (tp>1){
link(st[tp-1],st[tp]);
--tp;
}
m=_m;
return st[1];
}
int c[N],b[N];
int q[N*2],nq;
int dep[N];
int ans[N];
void dfs(int x,int fa=0){
dep[x]=d[x].dep();
for (EDGE *ei=last[x];ei;ei=ei->las)
dfs(ei->to,x),c[x]+=c[ei->to];
if (c[x])
q[++nq]=dep[fa]+1,q[++nq]=dep[x]+1;
else if (b[x])
q[++nq]=dep[x],q[++nq]=dep[x]+1;
}
int main(){
scanf("%d",&n);
for (int i=1;i<=n;++i){
int x1,y1,x2,y2;
scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
d[++m]={x1,y1};
d[++m]={x2,y2};
ed[i]={d[m-1],d[m]};
}
sort(d+1,d+m+1,cmpd);
m=unique(d+1,d+m+1)-d-1;
for (int i=1;i<=m;++i)
id[d[i]]=i;
int rt=build();
for (int i=1;i<=n;++i){
int u=id[ed[i].u],v=id[ed[i].v];
int lca=id[LCA(d[u],d[v])];
c[u]++,c[v]++,c[lca]-=2;
b[lca]=1;
}
dfs(rt);
sort(q+1,q+nq+1);
int cnt=0,s=0,last[2]={-2,-2};
for (int i=1;i<=nq;++i){
s^=1;
if (i==nq || q[i]!=q[i+1]){
if (last[s]<=last[s^1])
cnt++;
last[s]=q[i];
}
}
if (last[0]>last[1])
cnt--;
printf("%d\n",cnt);
return 0;
}