UOJ719 【北大集训2021】经典游戏 题解
题目描述
给定一棵 \(n\) 个点的有根树,初始第 \(i\) 个点上有 \(a_i\) 颗棋子,每次操作可以任选一个节点 \(u\) ,并将 \(u\) 上的一个棋子移动到 \(u\) 子树内,无法移动者判负。
在游戏开始之前,先后手分别有一次发动特殊能力的机会:
- 先手可以把树根 \(r\) 换成任意一个 \(r\) 的邻点(可以不换)。
- 后手必须选择一个节点并在这个节点加上一颗棋子(不能不加)。
总共有 \(m\) 局游戏,每局游戏流程如下:
- 给定 \(x,y\) ,在第 \(x\) 个节点上放一颗棋子,树根变为 \(y\) 。
- 先手发动特殊能力。
- 后手发动特殊能力。
- 先后手轮流移动棋子。
对于每一局,求先手有多少种换根方式(不换也算一种),使得无论后手如何操作,先手必胜。
每次加的棋子不独立,对后面的游戏仍有影响。
数据范围
- \(1\le n,m\le10^6,0\le a_i\le10^9\)。
- \(1\le x,y\le n\) 。
时间限制 \(\texttt{8s}\) ,空间限制 \(\texttt{512MB}\) 。
分析
谨以此文,纪念我花了 \(10\) 个小时的时间,写完了一道 \(\texttt{CTT}\) 的签到题。
前面的博弈论非常套路。
首先所有的棋子互相独立,所以只需考虑单个棋子的 \(sg\) 值。
显然 \(sg\) 值可以递推: \(sg_u=mex_{v\in subtree(u)}\{sg_v\}\) 。
手玩一下容易发现,\(sg_u=u\) 向子树内走的最大步数。
由于整个游戏的 \(sg\) 值为所有棋子的异或和,所以 \(a_i\le 10^9\) 根本没用,只有 \(a_i\) 的奇偶性是有用的。
由于后手放一颗棋子能给棋局贡献 \([0,mxd_{r}]\) 之间的任意一个数,所以先手必胜等价于当前盘面的 \(sg\) 值 \(\gt mxd_r\) 。
至此,这道题和博弈论没有关系了。
容易想到一个 \(\mathcal O(nm)\) 的暴力:预处理 \(d_{r,u}\) 表示以 \(r\) 为根时 \(u\) 向子树内走的最大深度,维护 \(cur_r\) 表示以 \(r\) 为根时整个棋盘的 \(sg\) 值。
每次在 \(u\) 加入一颗棋子,对 \(cur_r\) 的贡献是 \(d_{r,u}\) ,询问时枚举根节点及其邻点即可。
这样做可以获得 \(45pts\) 的好成绩了。
考虑优化。
我们干的无非就是干这\(2\)件事:
- 维护对 \(cur\) 数组的修改, \(cur_r\gets cur_r\oplus d_{r,u}\) 。
- 查询满足 \(cur_v\gt d_{v,v}\) 的 \(r\) 的邻点 \(v\) 个数。
这个 \(d\) 的定义,看着就很长链剖分。
固定 \(1\) 号点为根,记 \(f_u,g_u\) 分别表示 \(u\) 向子树内走的最大、次大距离, \(h_u\) 表示 \(u\) 向子树外走的最大距离,长剖时还可以顺便求出 \(mxd_u=d_{u,u}\) 表示 \(u\) 到所有点的最大距离。
固定 \(u\) ,则 \(d_{r,u}\) 只有三种情况:
- \(r\) 在 \(u\) 子树外,\(d_{r,u}=f_u\) 。
- \(r=u\) 或 \(r\) 在 \(u\) 的轻子树中,\(d_{r,u}=mxd_u\) 。
- \(r\) 在 \(u\) 的重子树中, \(d_{r,u}=\max(g_u,h_u)\) 。
注意到重子树的 \(dfs\) 序连续,区间异或,单点求值,可以用树状数组维护。
接下来处理查询的问题,先把 \(u\) 及其邻点分成以下几类: \(u,fa_u,son_u\) 和 \(u\) 的轻儿子们。
前几个按照上面所说的树状数组单点查询就可以了,难点是最后一类。
\(\text{Key observation}\) :所有轻儿子的 \(mxd\) 相同,且都等于 \(mxd_u+1\) !
先考虑下面这个问题:给定集合 \(S\) ,多次询问,每次给定 \(x,y\) ,求 \(\sum\limits_{i\in S}[i\oplus x>y]\) 。
这是 \(\texttt{trie}\) 上的经典问题。如果 \(y\) 当前位是 \(0\) ,答案加上 \(x\) 往反方向走的贡献。然后朝着 \(x\oplus y\) 的方向走即可。
但是它对这道题有什么帮助呢?
对于每个节点 \(u\) ,我们希望维护轻儿子 \(v\) 的值 \(cur_v\oplus cur_u\) 构成的集合,查询时令 \(x=cur_u,y=mxd_v+1\) 即可。
怎么想到的?
多数情况下, \(u\) 和轻儿子 \(v\) 的修改相同,因此 \(cur_v\oplus cur_u\) 不会变化。
在节点 \(u\) 添加棋子时,除了维护 \(cur\) 数组以外,如果\(u\)是轻儿子,还要考虑它对父节点集合的影响。记录 \(val_u\) 表示实际插入父节点的权值,删除旧的贡献再加入新的贡献即可。
时间复杂度 \(\mathcal O((n+m)\log n)\) 。
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e6+5;
int n,q,u,v,idx,res,tot;
int c[maxn],f[maxn],g[maxn],h[maxn];
int fa[maxn],sz[maxn],dfn[maxn],son[maxn];
int mxd[maxn],val[maxn];
int cnt[40*maxn],root[maxn];
int ch[40*maxn][2];
vector<int> vec[maxn];
int read()
{
int q=0;char ch=getchar();
while(ch<'0'||ch>'9') ch=getchar();
while(ch>='0'&&ch<='9') q=10*q+ch-'0',ch=getchar();
return q;
}
void dfs1(int u,int father)
{
for(auto v:vec[u])
{
if(v==father) continue;
fa[v]=u,dfs1(v,u),sz[u]+=sz[v];
if(f[v]+1>f[u]) son[u]=v,g[u]=f[u],f[u]=f[v]+1;
else g[u]=max(g[u],f[v]+1);
}
sz[u]++;
}
void dfs2(int u)
{
dfn[u]=++idx,mxd[u]=max(f[u],h[u]);
if(son[u]) h[son[u]]=max(g[u],h[u])+1,dfs2(son[u]);
for(auto v:vec[u])
{
if(v==fa[u]||v==son[u]) continue;
h[v]=max(f[u],h[u])+1,dfs2(v);
}
}
void add(int x,int v)
{
while(x<=n) c[x]^=v,x+=x&(-x);
}
int sum(int x)
{
int res=0;
while(x) res^=c[x],x-=x&(-x);
return res;
}
int get(int u)
{
return sum(dfn[u]);
}
void modify(int l,int r,int v)
{
if(l>r) return ;
add(l,v),add(r+1,v);
}
void insert(int id,int v,int num)
{
for(int i=19,p=root[id];i>=0;i--)
{
if(!ch[p][v>>i&1]) ch[p][v>>i&1]=++tot;
p=ch[p][v>>i&1],cnt[p]+=num;
}
}
int query(int id,int x,int y)
{
int res=0;
for(int i=19,p=root[id];i>=0;i--)
{
if(!p) break;
int j=x>>i&1,k=y>>i&1;
if(!k) res+=cnt[ch[p][j^1]];
p=ch[p][j^k];
}
return res;
}
void update(int u)
{
modify(1,dfn[u]-1,f[u]);
if(son[u])
{
modify(dfn[son[u]],dfn[son[u]]+sz[son[u]]-1,max(g[u],h[u]));
modify(dfn[son[u]]+sz[son[u]],dfn[u]+sz[u]-1,mxd[u]);
}
modify(dfn[u]+sz[u],n,f[u]);
modify(dfn[u],dfn[u],mxd[u]);
if(fa[u]&&son[fa[u]]!=u)
{
insert(fa[u],val[u],-1);
val[u]=get(u)^get(fa[u]);
insert(fa[u],val[u],1);
}
}
bool check(int u)
{
return get(u)>=mxd[u]+1;
}
int main()
{
read(),n=read(),q=read();
for(int i=1;i<=n-1;i++)
{
u=read(),v=read();
vec[u].push_back(v),vec[v].push_back(u);
}
dfs1(1,0),dfs2(1);
for(int i=1;i<=n;i++)
{
int tmp=vec[i].size()-(fa[i]!=0)-(son[i]!=0);
root[i]=++tot;
if(tmp) insert(i,0,tmp);
}
for(int i=1;i<=n;i++) if(read()&1) update(i);
while(q--)
{
update(read()),u=read(),res=check(u);
if(fa[u]) res+=check(fa[u]);
if(son[u]) res+=check(son[u]);
res+=query(u,get(u),mxd[u]+1);
printf("%d\n",res);
}
return 0;
}
本文来自博客园,作者:peiwenjun,转载请注明原文链接:https://www.cnblogs.com/peiwenjun/p/16372912.html
浙公网安备 33010602011771号