[bzoj3729]Gty的游戏
来源:互联网 发布:cnc程式模拟软件 编辑:程序博客网 时间:2024/06/15 14:13
题目大意
给定一颗树,初始n个结点,1为根节点。每个结点上有一定的石子数。
现在你需要在线兹瓷三种操作:
1、询问以x为根的子树中进行组合游戏,双方轮流操作,每次操作可以将一个结点(在子树内且不为x)的不超过p个至少1个石子移至其父亲结点。问这个游戏先手是否必胜?
2、修改一个结点的石子数。
3、新建一个结点石子数为x,其父亲设为y(保证y已经建立)
一点姿势
我们需要解决以下两个博弈论问题:
1、nim游戏的改版,每次最多拿走m个石子。
那么
找规律及感性认识得
那我们干脆把初始石子数直接模m+1变为普通Nim游戏。
2、有n级台阶,从0级开始数到n级。每级上都有一定得石子。每次可以把一个阶梯的石子往下移,0级阶梯的不能移,不能操作者输。
这个比较机智!结论是:我们只在乎奇数层的石子数,然后把每个奇数层当作一堆石子,那么该游戏等价于nim游戏。
为什么呢?
移动偶数层的石子,我们可以把该层下的石头拿等量继续往下,相当于这些石子还在偶数层。移动奇数层的石子,我们视为它消失了。
于是就是经典nim游戏了。
splay维护dfs序
我们用dfs序来表示这个树。
那么询问操作相当于询问区间深度与x深度奇偶性不同的数的nim和,修改操作就直接改,添加操作就是在一个数后添加一个数。
我们用splay维护dfs序,现在的问题是如何获取询问操作询问的是哪一个区间?
左端点肯定是x,右端点呢?传统做法应该维护size,但那样我们需要兹瓷size的动态维护涉及链修改,这里可以利用splay搞一波。
结论:在最后添加虚拟结点深度设为0,那么对于x,找到其在dfs序右边最近的一个点y满足d[y]<=d[x],那么[x,y)即是询问区间。至于为什么要虚拟结点这个自己yy即可得。
那么用splay维护区间深度为奇数的点nim和、所有点的nim和、所有点深度最小值即可。
#include<cstdio>#include<algorithm>#define fo(i,a,b) for(i=a;i<=b;i++)using namespace std;typedef long long ll;const int maxn=100000+10;int key[maxn],d[maxn],father[maxn],tree[maxn][2],sum[maxn],num[maxn],cnt[maxn];//sum quan xor num ji xor cnt mindint h[maxn],go[maxn*2],next[maxn*2],id[maxn];int i,j,k,l,t,n,m,p,tot,top,root,mask;void add(int x,int y){ go[++top]=y; next[top]=h[x]; h[x]=top;}void update(int x){ sum[x]=sum[tree[x][0]]^sum[tree[x][1]]^key[x]; num[x]=num[tree[x][0]]^num[tree[x][1]]; if (d[x]%2) num[x]^=key[x]; cnt[x]=d[x]; if (tree[x][0]) cnt[x]=min(cnt[x],cnt[tree[x][0]]); if (tree[x][1]) cnt[x]=min(cnt[x],cnt[tree[x][1]]);}int pd(int x){ if (x==tree[father[x]][0]) return 0;else return 1;}void rotate(int x){ int y=father[x],z=pd(x); father[x]=father[y]; if (father[y]) tree[father[y]][pd(y)]=x; tree[y][z]=tree[x][1-z]; if (tree[x][1-z]) father[tree[x][1-z]]=y; tree[x][1-z]=y; father[y]=x; update(y); update(x);}void splay(int x,int y){ while (father[x]!=y){ if (father[father[x]]!=y) if (pd(x)==pd(father[x])) rotate(father[x]);else rotate(x); rotate(x); }}void dfs(int x,int y){ if (y) d[x]=d[y]+1; tree[root][1]=x; father[x]=root; update(root); splay(x,0); root=x; int t=h[x]; while (t){ if (go[t]!=y) dfs(go[t],x); t=next[t]; }}int find(int x,int y){ if (tree[x][0]&&cnt[tree[x][0]]<=y) return find(tree[x][0],y); else if (d[x]<=y) return x; else return find(tree[x][1],y);}int main(){ //freopen("gty.in","r",stdin); scanf("%d%d",&n,&p); fo(i,1,n){ ll x; scanf("%lld",&x); id[i]=++tot; x%=(p+1); key[tot]=x; } fo(i,1,n-1){ scanf("%d%d",&j,&k); add(j,k);add(k,j); } dfs(1,0); tree[root][1]=++tot; father[tot]=root; update(root); splay(tot,0); root=tot; scanf("%d",&m); mask=0; while (m--){ scanf("%d",&t); if (t==1){ scanf("%d",&j); //j^=mask; j=id[j]; splay(j,0); root=j; k=find(tree[j][1],d[j]); splay(k,0); root=k; splay(j,k); if (d[j]%2==0) t=num[tree[j][1]];else t=sum[tree[j][1]]^num[tree[j][1]]; if (t) printf("MeiZ\n"),mask++;else printf("GTY\n"); } else if (t==2){ scanf("%d",&j); //j^=mask; j=id[j]; ll x; scanf("%lld",&x); //x^=mask; x%=(p+1); splay(j,0); root=j; key[j]=x; update(j); } else{ scanf("%d%d",&j,&k); //j^=mask;k^=mask; j=id[j]; id[k]=++tot; k=id[k]; d[k]=d[j]+1; ll x; scanf("%lld",&x); //x^=mask; x%=(p+1); key[k]=x; splay(j,0); root=j; l=tree[j][1]; tree[j][1]=k; father[k]=j; tree[k][1]=l; if (l) father[l]=k; update(k); update(j); /*splay(k,0); root=k;*/ } }}
树上分块大法好
实际上这道题我们是可以树上分块的,然后每块的维护信息相当于上文提到的spaly方法所要维护的信息。
为什么要提分块?别忘了这是gty系列!
- BZOJ3729: Gty的游戏
- [bzoj3729]Gty的游戏
- [bzoj3729]Gty的游戏
- [bzoj3729]Gty的游戏
- bzoj3729 Gty的游戏
- BZOJ3729: Gty的游戏
- [BZOJ3729]Gty的游戏/[JZOJ4759]石子游戏
- 【bzoj3729】【GTY的游戏】【阶梯博弈+splay】
- 【BZOJ3729】Gty的游戏,博弈+splay
- [BZOJ3729]Gty的游戏(博弈论+Splay)
- [BZOJ3729]Gty的游戏(dfs序+splay)
- [博弈 && Splay维护DFS序]BZOJ3729 .Gty的游戏
- BZOJ 3729 Gty的游戏
- BZOJ 3729: Gty的游戏
- [Splay] BZOJ 3729 Gty的游戏
- bzoj 3729: Gty的游戏 (博弈+splay)
- bzoj 3729: Gty的游戏 splay+dfs序+阶梯博弈
- 【转】GTY(())的内部
- 生产者消费者模式
- 蓝桥杯 历届试题 带分数
- Activit Modeler设计器汉化
- AndroidStudio修改单行注释灰不拉基的颜色
- FTC334K 触摸开关
- [bzoj3729]Gty的游戏
- Android 应用程序获得版本号
- Hbase分布式安装部署过程
- 输出全排列--循环+递归
- 浅谈 hadoop 文件合并
- iOS 单例的滥用和用依赖注入替代
- 最大滑动窗口
- 创建和使用动态库DLL
- WARN No appenders could be found for logger的解决方法