=== ===
这里放传送门
=== ===
题解
首先看这道题的博弈部分,也就是如何得到答案。对于单个节点来说,这就是一个简单的巴什博弈问题,每次可以拿走[1..L]个的话,只需要把石子个数对L+1取模就可以得到它的SG值了。如果考虑树上操作这就是一个阶梯博弈,每次只需要考虑奇数层上的石子。那么只需要对每棵子树维护它的奇数层SG值和偶数层SG值就可以了。因为有插入节点的操作所以dfs序要用Splay来维护。
这道题里面最关键的操作是插入节点的操作。因为访问子树的时候要知道它的入栈点和出栈点,也就是in和out,而如果插入了一个节点就有可能引起很多节点out值的变化。
一个节点的in显然就是它自己,但out值是它子树里最后一个被遍历的节点,也就是它一定是一个叶子节点。那么如果在某个叶子节点下面接了一个节点,就有至少一个节点的out会变化。而我们可以发现这个寻找out的关系是具有传递性的,比如树上某个节点x把u当做它的out,而u当前的out节点指向新接入的儿子v,那么x的out也应该指向v。这就启示我们可以用并查集来维护这个东西,把每个节点并到它的out上去就可以了。
而为了让修改out的次数尽量少,如果接入新节点的那个父亲不是叶子节点,我们可以通过适当处理让所有节点的out都不变,方法就是把新节点的dfs序紧挨着父节点放在它后面。比如当前有dfs序:u-v-w,v和w都是u的儿子,那么显然u的out是w。如果现在还要给u接一个新儿子x,那么我们可以把dfs序修改成:u-x-v-w,这样的话所有已有节点的in和out都不会变。而如果修改成:u-v-w-x,u的out就变了,就造成了不必要的修改。
代码
#include<map>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int n,L,m,tot,v[100010],p[100010],a[200010],nxt[200010],lastans,deep[100010],num[100010],cnt;
map<int,int>hash;
struct Node{
Node *ch[2],*fa;
int Osum,Esum,pos,val;
Node();
void count();
bool pl(){return this==fa->ch[1];}
Node* Pre();
Node* Nxt();
}*null,*Root,*in[100010],*out[100010],P[100010];
Node::Node(){ch[0]=ch[1]=fa=null;Osum=Esum=pos=val=0;}
void Node::count(){
Osum=ch[0]->Osum^ch[1]->Osum;
Esum=ch[0]->Esum^ch[1]->Esum;
if (deep[pos]&1) Osum^=val;
else Esum^=val;
}
Node* Node::Pre(){
Node *k=this;
if (ch[0]!=null){
k=ch[0];
while (k->ch[1]!=null) k=k->ch[1];
return k;
}
while (k==k->fa->ch[0]) k=k->fa;
return k->fa;
}
Node* Node::Nxt(){
Node *k=this;
if (ch[1]!=null){
k=ch[1];
while (k->ch[0]!=null) k=k->ch[0];
return k;
}
while (k==k->fa->ch[1]) k=k->fa;
return k->fa;
}
void add(int x,int y){
tot++;a[tot]=y;nxt[tot]=p[x];p[x]=tot;
}
void Rotate(Node *k){
Node *r=k->fa;
if (r==null||k==null) return;
int x=k->pl()^1;
r->ch[x^1]=k->ch[x];
if (k->ch[x]!=null)
r->ch[x^1]->fa=r;
if (r->fa!=null)
r->fa->ch[r->pl()]=k;
else Root=k;
k->fa=r->fa;
r->fa=k;
k->ch[x]=r;
r->count();k->count();
}
void Splay(Node *r,Node *tar){
for (;r->fa!=tar;Rotate(r))
if (r->fa->fa!=tar)
Rotate(r->pl()==r->fa->pl()?r->fa:r);
}
Node* Find_out(Node *x){
if (out[x->pos]==x) return out[x->pos];
out[x->pos]=Find_out(out[x->pos]);
return out[x->pos];
}
int Getans(Node *x){
Node *tmp=in[x->pos];
int num;
Splay(tmp->Pre(),null);
tmp=Find_out(x);
Splay(tmp->Nxt(),Root);
num=x->pos;
if (deep[num]&1) return Root->ch[1]->ch[0]->Esum;
else return Root->ch[1]->ch[0]->Osum;
}
void dfs(int u,int fa){
deep[u]=deep[fa]+1;
num[++cnt]=u;
in[u]=P+u;
for (int i=p[u];i!=0;i=nxt[i])
if (a[i]!=fa) dfs(a[i],u);
out[u]=P+num[cnt];
}
Node *build(int l,int r){
if (l>r) return null;
int mid=(l+r)>>1;
Node *k=P+num[mid];
if (mid==0||mid==n+1)
k=new Node();
k->ch[0]=build(l,mid-1);
k->ch[1]=build(mid+1,r);
if (k->ch[0]!=null) k->ch[0]->fa=k;
if (k->ch[1]!=null) k->ch[1]->fa=k;
k->count();return k;
}
int main()
{
null=new Node;*null=Node();
scanf("%d%d",&n,&L);
for (int i=1;i<=n;i++){
int x;scanf("%d",&x);
x%=(L+1);v[i]=x;
P[i]=Node();hash[i]=i;
P[i].pos=i;P[i].val=v[i];
}
for (int i=1;i<n;i++){
int x,y;scanf("%d%d",&x,&y);
add(x,y);add(y,x);
}
dfs(1,0);
Root=build(0,n+1);
scanf("%d",&m);
for (int i=1;i<=m;i++){
int type;scanf("%d",&type);
switch (type){
case 1:{
int v;scanf("%d",&v);
v^=lastans;
v=hash[v];
if (Getans(P+v)==0) printf("GTY\n");
else{lastans++;printf("MeiZ\n");}
break;
}
case 2:{
int x,y;scanf("%d%d",&x,&y);
x^=lastans;y^=lastans;
x=hash[x];y%=(L+1);
Splay(in[x]->Pre(),null);
Splay(in[x]->Nxt(),Root);
Root->ch[1]->ch[0]->val=y;
Root->ch[1]->ch[0]->count();
Root->ch[1]->count();
Root->count();break;
}
case 3:{
int u,v,x;
Node *now;
scanf("%d%d%d",&u,&v,&x);
u^=lastans;v^=lastans;x^=lastans;
u=hash[u];v=hash[v]=++n;
P[v]=Node();now=P+v;
in[v]=out[v]=now;
deep[v]=deep[u]+1;//修改deep必须在count之前
now->pos=v;
now->val=x%(L+1);
now->count();
Splay(in[u],null);
Splay(in[u]->Nxt(),Root);
Root->ch[1]->ch[0]=now;
now->fa=Root->ch[1];
if (out[u]==in[u]) out[u]=now;//如果父节点是叶子就要修改out
Root->ch[1]->count();
Root->count();break;
}
}
}
return 0;
}
偏偏在最后出现的补充说明
用Splay维护dfs序是常见的思路啦。
常见的博弈模型一定要记牢,很多简单的博弈问题都是直接套模型就可以解决的。