zoukankan      html  css  js  c++  java
  • 【洛谷6665】[清华集训2016] Alice 和 Bob 又在玩游戏(博弈论+Trie树)

    点此看题面

    • 给定一张(n)个点的有根树森林。
    • (Alice)(Bob)轮流操作,一个人每次可以删去一个点到根的所有节点,先不能操作者输。
    • 判断谁有必胜策略。
    • 数据组数(le10,nle10^5)

    博弈论:(SG)函数

    考虑设(SG(x))表示(x)的子树对应的(SG)函数。

    首先,显然对于若干棵不同的树之间的决策是互不关联的,因此它们的总(SG)值就是各个(SG)值的异或和。

    因此我们的核心问题就在于如何在已求出(x)子树内所有(SG(y))的前提下,推出(SG(x))

    对于这种问题我们只能借助定义来求解,即想办法求出所有后继情况的(SG)值的(mex)

    一个最简单的后继情况就是删去(x),那么就相当于把子节点拆成了若干棵森林,那么这种情况的(SG)值就是所有子节点(SG)值的异或和(不妨设它为(t),这个值之后还会用到)。

    否则,我们可能会删去某个子节点(y)子树的某一个点,假设删去这个点后(y)子树的(SG)值变成了(sg)

    由于不管删去(x)子树内的哪个点,都将导致(x)被删去,因此除(y)以外的其余子节点也相当于变成了若干棵独立的树。

    也就是说,这种情况下的(SG)值应该是(sgoplus(toplus SG(y))),也就是((sgoplus SG(y))oplus t)

    考虑对于固定的(x)(t)是可以直接求出的,因此我们关键是要维护出所有可能的(sgoplus SG(y)),而这其实是一个可以由(y)决定的量,不受其余子树的干涉,只需要把不同子树的可能值合并起来即可。

    所以我们只需要知道(sg)这种决策由(y)(x)发生的转变,根据前面的结论(sg)将会变化(SG(y)oplus t),而为了保持形式不变需要给它异或上(SG(x)),总共给它异或上了(toplus x),而这恰是一个只与(x)有关的量。

    全局异或可以用(Trie)树维护,合并子树信息可以用(Trie)树合并,查询(mex)可以(Trie)树上二分,于是这道题就解决了。

    代码:(O(nlogn))

    #include<bits/stdc++.h>
    #define Tp template<typename Ty>
    #define Ts template<typename Ty,typename... Ar>
    #define Reg register
    #define RI Reg int
    #define Con const
    #define CI Con int&
    #define I inline
    #define W while
    #define N 100000
    #define add(x,y) (e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y)
    using namespace std;
    int n,m,ee,lnk[N+5];struct edge {int to,nxt;}e[2*N+5];
    namespace FastIO
    {
    	#define FS 100000
    	#define tc() (FA==FB&&(FB=(FA=FI)+fread(FI,1,FS,stdin),FA==FB)?EOF:*FA++)
    	char oc,FI[FS],*FA=FI,*FB=FI;
    	Tp I void read(Ty& x) {x=0;W(!isdigit(oc=tc()));W(x=(x<<3)+(x<<1)+(oc&15),isdigit(oc=tc()));}
    	Ts I void read(Ty& x,Ar&... y) {read(x),read(y...);}
    }using namespace FastIO;
    class Trie
    {
    	private:
    		#define PU(x) (O[x].Sz=O[O[x].S[0]].Sz+O[O[x].S[1]].Sz)
    		#define PD(x,d) (O[x].G&&(T(O[x].S[0],O[x].G,d-1),T(O[x].S[1],O[x].G,d-1),O[x].G=0))
    		#define T(x,v,d) ((v)>>(d)&1&&(swap(O[x].S[0],O[x].S[1]),0),O[x].G^=v)
    		int Nt;struct node {int Sz,G,S[2];}O[N*100];
    	public:
    		I void Clear() {W(Nt) O[Nt].Sz=O[Nt].G=O[Nt].S[0]=O[Nt].S[1]=0,--Nt;}//清空
    		I void U(CI rt,CI v,CI d=15) {T(rt,v,d);}//全局标记
    		I void A(int& rt,CI v,CI d=20)//插入
    		{
    			!rt&&(rt=++Nt),~d?(PD(rt,d),A(O[rt].S[v>>d&1],v,d-1),PU(rt)):O[rt].Sz=1;//Sz维护不同元素个数
    		}
    		I int Mex(CI rt,CI d=20)//Trie树上二分出mex
    		{
    			if(!rt||!~d) return 0;PD(rt,d);
    			return O[O[rt].S[0]].Sz==(1<<d)?(1<<d|Mex(O[rt].S[1],d-1)):Mex(O[rt].S[0],d-1);//只有左子树满时才能向右走
    		}
    		I void Merge(int& x,CI y,CI d=20)//Trie树合并
    		{
    			if(!x||!y) return (void)(x|=y);~d&&(PD(x,d),PD(y,d),
    			Merge(O[x].S[0],O[y].S[0],d-1),Merge(O[x].S[1],O[y].S[1],d-1),0);//递归合并
    		}
    }T;
    int SG[N+5],Rt[N+5];I void dfs(CI x,CI lst=0)//dfs遍历
    {
    	RI i,t=0;for(i=lnk[x];i;i=e[i].nxt) e[i].to^lst&&(dfs(e[i].to,x),T.Merge(Rt[x],Rt[e[i].to]),t^=SG[e[i].to]);//合并Trie树,统计异或和
    	T.U(Rt[x],t),T.A(Rt[x],t),T.U(Rt[x],SG[x]=T.Mex(Rt[x]));//先给全局异或t修改完,然后插入删去x的后继状态t,再求出当前点SG并异或给全局
    }
    int main()
    {
    	RI Tt,i,x,y,t=0;read(Tt);W(Tt--)
    	{
    		for(read(n,m),T.Clear(),ee=0,i=1;i<=n;++i) lnk[i]=SG[i]=Rt[i]=0;//清空
    		for(i=1;i<=m;++i) read(x,y),add(x,y),add(y,x);
    		for(t=0,i=1;i<=n;++i) !Rt[i]&&(dfs(i),t^=SG[i]);puts(t?"Alice":"Bob");//不同树的SG直接异或
    	}return 0;
    }
    
    败得义无反顾,弱得一无是处
  • 相关阅读:
    Jenkins安装
    Python操作yaml文件
    class 中构造函数与析构函数
    python发送邮件(yagmail模块)
    filter、map函数的区别
    python redis操作
    多个 python的pip版本选择
    python Excel操作
    python MD5操作
    缓存淘汰算法之LRU实现
  • 原文地址:https://www.cnblogs.com/chenxiaoran666/p/Luogu6665.html
Copyright © 2011-2022 走看看