题意:
B君在围观一群男生和一群女生玩游戏,具体来说游戏是这样的:
给出一棵n个节点的树,这棵树的每条边有一个权值,这个权值只可能是0或1。 在一局游戏开始时,会确定一个节点作为根。接下来从女生开始,双方轮流进行 操作。当一方操作时,他们需要先选择一个不为根的点,满足该点到其父亲的边权为1; 然后找出这个点到根节点的简单路径,将路径上所有边的权值翻转(即0变成1,1 变成0 )。
当一方无法操作时(即所有边的边权均为0),另一方就获得了胜利。
如果在双方均采用最优策略的情况下,女生会获胜,则输出“Girls win!”,否则输 出“Boys win!”。
为了让游戏更有趣味性,在每局之间可能会有修改边权的操作,而且每局游戏指 定的根节点也可能是不同的。
具体来说,修改边权和进行游戏的操作一共有m个,具体如下:
“0 x”表示询问对于当前的树,如果以x为根节点开始游戏,哪方会获得胜利。
“1 x y z ”表示将x和y之间的边的边权修改为z。
B君当然知道怎么做啦!但是他想考考你。
思路:
树上博弈,一般都是思维模拟找规律,但今天做这题因为好久没做只写出一个超时的版本...分析的时候自己完全没有去侧重根x去分析,写了一张纸不同情形的子树去分析,最终发现一棵子树中不管怎么去操作最终操作次数的奇偶性是确定的,但是不足以解决这题= =
正解是,经分析发现无论操作哪个节点,这个节点都会使其所在子树中与根直接相连的那条边翻转。那么再根据游戏的规则以及子树的性质,会发现若你面对当前这条与根相连的边的权值为1时,对方通过子树上任意点来翻转当前边,你都能再次进行翻转。如果你面对权值为0,那么要么你不能进行操作,要么不管你进行什么操作对方都还能进行操作。
所以对于当前根为x,只需统计与x相连的所有边1的个数,为奇先手必胜,为偶先手必输。
代码:
#include <bits/stdc++.h>
using namespace std;
const int maxn = 4e4+5;
const int bas = 4e4+1;
unordered_map<int, int> has;
vector<int> g[maxn];
int t, n, m, ans[maxn];
int main()
{
//freopen("in.txt", "r", stdin);
for(scanf("%d", &t); t--;)
{
scanf("%d %d", &n, &m);
for(int i = 1; i <= n; ++i) g[i].clear();
for(int i = 1; i < n; ++i)
{
int u, v, w;
scanf("%d %d %d", &u, &v, &w);
g[u].push_back(v);
g[v].push_back(u);
if(u > v) swap(u, v);
has[u*bas+v] = w;
}
for(int i = 1; i <= n; ++i)
{
int tmp = 0;
for(int j = 0; j < g[i].size(); ++j)
{
int v = g[i][j], u = i;
if(u > v) swap(u, v);
tmp += has[u*bas+v];
}
ans[i] = tmp&1;
}
for(int i = 1; i <= m; ++i)
{
int key, x, y, z;
scanf("%d", &key);
if(key)
{
scanf("%d %d %d", &x, &y, &z);
if(x > y) swap(x, y);
if(z != has[x*bas+y])
{
ans[x] ^= 1;
ans[y] ^= 1;
has[x*bas+y] = z;
}
}
else
{
scanf("%d", &x);
if(ans[x]&1) puts("Girls win!");
else puts("Boys win!");
}
}
}
return 0;
}
继续加油~