【Christmas Game】【CodeCraft-21 and Codeforces Round #711 (Div. 2)】【Nim-博弈】【树形DP】【拆分树】

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;    
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值