组合游戏(Circles Game,HDU 5299)

来源:互联网 发布:西瓜影音mac版 官方 编辑:程序博客网 时间:2024/06/05 14:39

很容易想到这是一个组合游戏。每个子游戏都是一棵有根树上的游戏,最后的答案就是每个子游戏SG值的Nim和。

有根树上的游戏就是给你一颗有根树,每次操作可以随便选取一个节点,并把这个节点所在的子树都删掉,不能操作者输。

那怎么计算每棵树的SG值呢?一开始尝试用经典的递推法去求,但是发现树的结构种类太多,不可能穷举,每种结构的后续状态也非常多,也不可能用每个后续节点来转移,而且记录树的信息很多,没有办法用简单的方法描述一棵树以及这个树的后续状态。所以把目标转向树形DP。希望只通过在子树上保存一些信息,然后根据这些信息快速地算出当前节点的SG值,同时维护信息。我一直考虑在子树上保存SG值以及mex()之类的信息,但是依然找不到一个简单靠谱的转移方法,既i避免不了穷举,更没有办法计算答案。最后没办法,只好手动计算各种不同结构的树的SG值,慢慢地找到了一些规律,就是如果两颗子树完全相同,那就可以同时删去,因为如果这是必败态,那么对手只需要模仿你的操作,你依然是必败态。如果这是必胜态,那么必然要让对方先动这两颗子树,你来模仿即可。后来想通了SG值可以完整地描述一个状态在组合游戏下的特征。换句话说,不仅仅是两颗完全相同的子树可以完全删去,其实只要他们的SG值相同,就可以当做是完全相同的子树,也就可以同时删去。后来又想通了SG值的意义,如果三个SG值异或起来等于0,那么这个游戏其实相当于两个SG值异或起来等于0的游戏。两个子游戏我们是通过模仿对方来取胜,三个子游戏其实也一样,只不过模仿的过程没有那么显然。

一边手算,一边思考,尝试寻找当前节点SG值与子节点SG值的关系。慢慢发现了我手算的所有数据都满足一个规律,那就是SG(u)=(SG(v)的异或和)+1。感觉自己应该是对的,但是不明白为什么,所以就从结论出发,尝试证明,最后想通了。


假如不允许取根节点,那么当前树的SG值就等于所有子树的SG值异或起来。现在允许取根节点,那么所有的状态都多了一个后续状态----空集,所以所有的SG值都+1,当前节点的SG值也不例外。


通过本题,我对SG值有了更深入的理解。同时也更加明白了就算你不知道一些冷门的定理,你依然可以通过思考和找规律猜出来甚至证明出来的道理。感觉很多时候你都需要这样去摸索的。


其实个定理不算冷门,只是自己知道的太少了。但是能够自己找到规律是应该的。


关于树上删边游戏的一些资料:

别人的题解:http://www.cnblogs.com/huoxiayu/p/4710749.html

百度文库资料:https://wenku.baidu.com/view/379e8baaa58da0116d174924.html


本题在建树时需要一些优化,不能O(n^2)暴力连边。

本题时间卡得很紧,可能需要把细节尽量优化好。

理论上来讲最坏还是O(n^2)的,但是就是给优化过了。

可能也就多校训练赛才会出那么经典的题目+这样卡优化吧。


代码:

#include<bits/stdc++.h>using namespace std;const int maxn = 20010;typedef long long ll;struct Point{    int x,y;    Point(){};    Point(int x,int y):x(x),y(y){}    void Read()    {        scanf("%d %d",&x,&y);    }};struct Circle{    Point p;    int r;    Circle(){};    Circle(Point p,int r):p(p),r(r){}    void Read()    {        p.Read();        scanf("%d",&r);    }    bool operator < (const Circle& rhs) const    {        return r<rhs.r;    }};ll Dist2(Point A,Point B){    int x = A.x-B.x;    int y = A.y-B.y;    return 1ll*x*x+1ll*y*y;}bool In(Circle A,Circle B){    int d = B.r-A.r;    return A.r<B.r&&1ll*d*d>Dist2(A.p,B.p);}int n;Circle c[maxn];vector<int>G[maxn];int p[maxn];int dfs(int u){    int ret=0;    for(int i=0;i<(int)G[u].size();i++)        ret^=dfs(G[u][i]);    return ret+1;}void solve(){    scanf("%d",&n);    for(int i=1;i<=n;i++)    {        c[i].Read();        G[i].clear();        p[i]=0;    }    sort(c+1,c+1+n);    for(int i=1;i<=n;i++) for(int j=i+1;j<=n;j++) if(In(c[i],c[j]))    {        p[i]=j;        break;    }    for(int i=1;i<=n;i++) if(p[i]) G[p[i]].push_back(i);    int nim = 0;    for(int i=1;i<=n;i++) if(!p[i]) nim^=dfs(i);    if(nim) puts("Alice");    else puts("Bob");}int main(){    int T;    scanf("%d",&T);    while(T--) solve();    return 0;}


0 0
原创粉丝点击