CodeCraft-21 and Codeforces Round #711 (Div. 2)
Christmas Game
Nim-博弈
树形DP
拆分树
牛客链接 https://ac.nowcoder.com/acm/contest/34655/D
codeforces链接 https://codeforces.com/contest/1498/problem/F
step1
在一个普通的Nim
游戏中,有n
堆石子,每堆石子数量为a[i]
,每次可以在任意一堆取出任意数量的石子,此时胜负由a[1]^a[2]^...^a[n]
是否等于零决定。
step2
对于k==1
的情况,令dis
为某个结点到根结点的深度,显然有dis%2==0
时,该节点对整个游戏最终的胜负无贡献。因为无论第一个人从该结点移走了多少,第二个人只需要将这部分继续上移即可。同时对于深度为奇数的结点上的值,经过一次移动后会成为深度为偶数的结点上的值,这样最终的胜负实际取决于所有深度为奇数的结点异或和。
step3
对于k!=1
的情况,显然深度模k
余数不同的结点是完全独立的,因此可以把原树拆成许多k==1
的树。实际上可以转化为Nim
游戏
step4
那么如何拆分这颗树呢?
注意到数据范围中n<=1e5 && k<=20
,最大空间在Cnk
级别。
假设以node1
为根结点,使用一个二维数组dp[node][depth]
记录下以node
为根节点的子树中,所有到node
结点深度为depth
的结点值的异或和,那么:
- 对于以
node1
为根结点情况,只要找出depth%2!=0
时所有dp[node1][depth]
做异或和即可。 - 对于以
node1
子节点node1Son
为根结点情况,可以通过除去node1Son
影响后将深度为k
的值赋给深度为k+1
的值,最后再次并入node1Son
影响得到新的深度异或值。
同时,这个二维数组可以通过树形DP
获得:
// k2 = 2 * k
void dfs(VVI &adj, int node, int prev){
dp[node][0] = a[node];
for(auto neigh : adj[node]){
if(neigh == prev)
continue;
dfs(adj, neigh, node);
for (int rem = 1; rem < k2; rem++)
dp[node][rem] ^= dp[neigh][rem - 1];
dp[node][0] ^= dp[neigh][k2 - 1];
}
}
step5(为什么要使用k2)
现在我们需要进一步将空间压缩到Cnk
级别,由于深度模k
后的结点属于同一颗拆分的树,如果将深度模2k
,则余数在[k, 2k-1]
范围内才会对答案有贡献,即在拆分的数中深度为奇数。
完整代码(来自codeforces)
#include <bits/stdc++.h>
using namespace std;
// classify nodes into odd or even.
// depending on their depth relative to the root.
// Note that even steps do not affect the game state.
// Once they are on an even step,
// they no longer contribute to the game state.
// At each node x, we store a vector of size D(n) = 2K
// where D[x][i] is the xorsum of all nodes having their depth = i
// relative to node x
// nodes at depth i is at depth i+1 for my child nodes
const int maxn = 1e5 + 7;
const int K = 21;
using VI = vector<unsigned int>;
using VVI = vector<vector<unsigned int>>;
VVI dp(maxn, VI(2 * K));
VI a(maxn);
vector<bool> win(maxn);
int n, k, k2;
void dfs(VVI &adj, int node, int prev){
dp[node][0] = a[node];
for(auto neigh : adj[node]){
if(neigh == prev)
continue;
dfs(adj, neigh, node);
for (int rem = 1; rem < k2; rem++)
dp[node][rem] ^= dp[neigh][rem - 1];
dp[node][0] ^= dp[neigh][k2 - 1];
}
}
void dfs2(const VVI &adj, const int node, const int prev, const vector<unsigned int> &my_xors){
vector<unsigned int> final_xors(k2);
for (int i = 0; i < k2; i++)
final_xors[i] = my_xors[i] ^ dp[node][i];
unsigned int odd_layer_xor = 0;
for (int i = k; i < k2; i++)
odd_layer_xor ^= final_xors[i];
win[node] = odd_layer_xor > 0;
for (auto neigh:adj[node]){
if (neigh == prev)
continue;
auto xor_send = final_xors;
// remove all contribution of this subtree
for (int i = 1; i < k2; i++)
xor_send[i] ^= dp[neigh][i - 1];
xor_send[0] ^= dp[neigh][k2 - 1];
// whatever was depth k for me is depth k+1 for my child node
int last = xor_send.back();
for (int i = k2 - 1; i > 0; i--)
xor_send[i] = xor_send[i - 1];
xor_send[0] = last;
dfs2(adj, neigh, node, xor_send);
}
}
int main(){
cin.sync_with_stdio(0), cin.tie(0), cout.tie(0);
cin >> n >> k; // nodes layers
k2 = 2 * k;
VVI adj(n + 1);
for (int i = 0; i < n - 1; i++){
int x, y;
cin >> x >> y;
adj[x].push_back(y);
adj[y].push_back(x);
}
for (int i = 1; i <= n; i++)
cin >> a[i];
dfs(adj, 1, 0);
dfs2(adj, 1, 0, vector<unsigned int>(k2));
for (int i = 1; i <= n; i++) cout << (win[i] ? 1 : 0) << " ";
return 0;
}