CF上部分树形DP练习题

本次 5 道题均来自Codeforce

关于树形DP的算法讲解:Here

791D. Bear and Tree Jumps

如果小熊每次能跳跃的距离为1,那么问题变为求树上任意两点之间距离之和。

对于每一条边sum1和sum2分别表示边的左右点数,ans=各边的sum1*sum2之和即为答案。

而本题最大跳跃距离为k,答案变为(ans+sum)/k。sum为每一条边需要多走x步才能整除k的x之和。
树上任意两点间距离len=depth[x1]+depth[y1]-2*depth[f],f表示点x1和点y1的最近公共祖先。
计算sum的方法:dp[i][j]表示到i点的距离对k取摸为j的点的总数。
则对于任意两点a和b,dis需要满足(len[a][b]+dis)%k==0。
每当搜索到一个点时,用O(k^2)的方法枚举所有情况。

const int N = 2e5 + 10;
vector<int>e[N];
ll f[N][5] = {0}, a[N], ans;
int n, k;
void dfs(int u, int fa, int num) {
    f[u][num % k] = 1;
    a[u] = 1;
    for (int v : e[u]) {
        if (v == fa) continue;
        dfs(v, u, num + 1);
        for (int j = 0; j < k; ++j)
            for (int r = 0; r < k; ++r) {
                int dis = (j + r - num * 2) % k;
                int rev = (k - dis) % k;
                ans += rev * f[u][j] * f[v][r];
            }
        a[u] += a[v];
        for (int j = 0; j < k; ++j) f[u][j] += f[v][j];
        ans += (n - a[v]) * a[v];
    }
}
int main() {
    cin.tie(nullptr)->sync_with_stdio(false);
    cin >> n >> k;
    for (int i = 1, x, y; i < n; ++i) {
        cin >> x >> y;
        e[x].push_back(y);
        e[y].push_back(x);
    }
    dfs(1, -1, 0);
    cout << ans / k << "\n";
}

1120D. Power Tree

实在没想到该如何去写出输出方案

附一篇优秀题解:Here

1153D. Serval and Rooted Tree

似乎很板子?

叶子节点dp[i]=1

如果节点取max则dp[i]=min(dp[子节点们])

如果取min则dp[i]+=dp[子节点们]

答案就是 叶子节点个数+1-dp[1]

int main() {
    cin.tie(nullptr)->sync_with_stdio(false);
    int n; cin >> n;
    vector<int> m(n);
    for (int &x : m) cin >> x;
    vector<int> sz(n);
    vector<vector<int>> e(n);
    for (int i = 1, x; i < n; ++i) {
        cin >> x;
        e[--x].push_back(i);
    }
    function<int(int)> dfs = [&](int u) {
        if (e[u].empty()) {
            sz[u] = 1;
            return 1;
        }
        int x = 0;
        if (m[u] == 1) x = n;
        sz[u] = 0;
        for (int v : e[u]) {
            int y = dfs(v);
            sz[u] += sz[v];
            if (m[u] == 1) x = min(x, sz[v] - y);
            else x += y - 1;
        }
        if (m[u] == 1) return sz[u] - x;
        else return x + 1;
    };
    cout << dfs(0) << "\n";
}

735E. Ostap and Tree

1060E. Sergey and Subway

题意:给出一个树,把树上任意两个相隔一个点的点加一条边,问加完边之后任意两点的距离和是多少.

先把问题转化一下,求树上所有点对的边距离和,那么针对每一条边他的贡献就是 一端点数*另一端点数 这是所有的要使用她的点对。 那么问题就被简化了,针对这道题目,可以得到距离为偶数的点 \(x/2\),距离为奇数的为 \((x+1)/2\) ,那么就是奇偶层计算距离时需要加1,那么我们统计出这个再和之前每条边的贡献求和除2就是答案

const int N = 240000;
vector<int>e[N];
ll  cnt, ans, n, f[N]; // f[N] 该点下方的点数
void dfs(int u, int fa, int de) {
    f[u] = 1;
    cnt += de;
    for (int v : e[u]) {
        if (v == fa) continue;
        dfs(v, u, de ^ 1);
        f[u] += f[v];
    }
}
int main() {
    cin.tie(nullptr)->sync_with_stdio(false);
    cin >> n;
    for (int i = 1, x, y; i < n; ++i) {
        cin >> x >> y;
        e[x].push_back(y);
        e[y].push_back(x);
    }
    dfs(1, -1, 0);
    for (int i = 1; i <= n; ++i) ans += f[i] * (n - f[i]);
    ans += cnt * (n - cnt); //奇偶层的贡献
    cout << ans / 2;
}
posted @ 2021-08-12 16:49  Koshkaaa  阅读(738)  评论(0编辑  收藏  举报