题目链接:
http://lightoj.com/volume_showproblem.php?problem=1355
题目大意:
给一棵带边权的树,两个人分别给边涂色。边权代表了这条边可以涂色的次数。如果一条边的涂色次数没有用完,那么可以涂他子树的边。无法涂色者为负。
分析:
看到题目的第一想法:搜题解。
然后就发现这个是个没学过的green博弈。
green博弈详解:
https://blog.csdn.net/acm_cxlove/article/details/7854532
这道题就用到了最前面的把一棵树搞成一根竹竿的定理,所以后面的不用看了
可是这道题的边权不都是1。下面分析一下各种边权情况下结点sg值的求法。
首先要知道,根据green博弈的说法,当一个结点下面分叉时,要想办法给它搞成一根竹竿。
对于竹竿来说,每个结点的sg值就是它下面边的个数(边权都=1)。
并且如果知道一个点的sg值为x,那么可以等效认为这个点下面只连着x条边权=1的边。
题解上说如果AB边权值大于1,那么sg[A]=sg[B]^1。
但是没说咋推出来的,所以下面来推一下。
设a[n][x]表示AB边权为n,且B点的sg值为x时结点A的sg值。
1.当前结点下无边(边权=0),此时sg值为0。因为此时没有可以涂色的边了。(a[0][i]=0)
2.边权=1。此时就是green博弈。当前结点的sg值等于他所有子结点sg值+1的异或。(a[1][i]=i+1)
3.边权=2。假设这条边AB下面连着x条边权都=1的边时(即B点sg值=x):
此时求a[2][x]。
当这个边权=2的边下面啥都没有时,只有一种涂色方案:
其中AB就是当前边权=2的边,B下面没有边了。所以根据第一条,B点的sg值=0,这种涂色方案导致的后继状态就是把AB变成了一个边权=1的边。所以这个后继状态为a[1][0]=0+1=1。所以当前状态的a[2][0]=mex{1}=0。
下面有1条边时(B点sg值=1)有两种后继状态:
第一种还是把这个边变成了一个边权=1的边,所以此时a[1][1]=1+1=2。
第二种涂完之后就成了上面那个图。此时A点sg值=0。
所以当前状态的a[2][1]=mex{2,0}=1。
下面有两条边时(B点sg值=2)有三种后继状态:
第一种sg[1][2]=2+1=3;第二种A点sg值=a[2][0]=0;第三种A点sg值=a[2][1]=1。
所以当前状态的a[2][2]=mex{3,0,1}=2。
可以推出,AB边权=2时,a[2][i]=mex{i+1,0,1, … ,i-1}=i。
4.AB边权=3时,按照刚才的过程,当B点sg值=0时,只有一种方案,那就是把AB的边权变成2,所以当前的a[3][0]=mex{0}=1。
当B点sg值=1时,有两种后继状态:
第一种就是把AB变成了边权=2,且B点sg值=1时的情况,此时sg值=1
第二种就是上面分析的AB=2,B点sg值=0时的情况,此时sg值=1。
所以当前状态的a[3][1]=mex{1,1}=0。
当B点sg值=2时,有三种后继状态:
可以得出,三种后续状态的A点的sg值分别是2,1,0。
所以当前状态的a[3][2]=mex{2,1,0}=3。
每个状态的后继状态都是之前已经推出的状态。
可得:a[3][i]=mex{a[2][i],a[3][0],a[3][1], … ,a[3][i-1]},因为用到的值都在前面算出,所以可以打表。
可以发现,当AB=3时,若B点sg值是偶数,则A点sg值=B点sg值+1;否则A点sg值=B点sg值-1。
根据上述方法继续递推,可以发现,AB权值n为偶数时,a[n][i]=i(可以理解,AB权值为偶数时只要先手在AB上涂色,那么后手一定可以相应的涂色。所以相当于AB这段没有任何贡献,相当于可以把B下面连的东西直接放到A处。)
AB权值n为奇数时,a[n][i]=i%2==0?i+1:i-1。
以上过程推出了不同权值下结点sg值的规律,所以写代码时就不用这个a数组了。
我们直接用sg[A]数组表示结点A的sg值。
总结一下规律:
当AB权值=1时,sg[A]=sg[B]+1。
当AB权值为偶数时,sg[A]=sg[B]。
当AB权值为奇数时,sg[A]=sg[B]%2==1?sg[B]-1:sg[B]+1。(也就是题解上说的sg[A]=sg[B]^1)
根据这个规律用dfs搜一遍每个结点的sg值,最后根据根结点的sg值判断胜负就行了。
代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <vector>
#define N 1005
#define LL long long
using namespace std;
int sg[N];
vector<vector<pair<int,int> > >v(N);//to,length
void dfs_sg(int now,int father)
{
for(unsigned int i=0;i<v[now].size();i++)
if(v[now][i].first!=father)
dfs_sg(v[now][i].first,now);
for(unsigned int i=0;i<v[now].size();i++)
if(v[now][i].first!=father)
{
if(v[now][i].second==1)
sg[now]^=sg[v[now][i].first]+1;
else if(v[now][i].second%2==0)
sg[now]^=sg[v[now][i].first];
else
{
if(sg[v[now][i].first]%2)
sg[now]^=sg[v[now][i].first]-1;
else
sg[now]^=sg[v[now][i].first]+1;
}
}
}
int main()
{
int t;
scanf("%d",&t);
for(int tt=1;tt<=t;tt++)
{
memset(sg,0,sizeof(sg));
v=vector<vector<pair<int,int> > >(N);
int n;
scanf("%d",&n);
for(int i=0;i<n-1;i++)
{
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
v[a].push_back(make_pair(b,c));
v[b].push_back(make_pair(a,c));
}
dfs_sg(0,0);
if(sg[0])
printf("Case %d: Emily\n",tt);
else
printf("Case %d: Jolly\n",tt);
}
return 0;
}
高哥牛批!!!