zoukankan      html  css  js  c++  java
  • 树形dp的进阶 (一)

    ①树的重心的性质的运用

    ②缩点以后寻找规律  树的直径!

    ③树形dp上的公式转换

    ④和期望有关的树形dp + 一点排列组合的知识

    一:Codeforces Round #364 (Div. 1) B

    http://codeforces.com/problemset/problem/700/B

    题目大意:给你一棵树,给你k个树上的点对。找到k/2个点对,使它在树上的距离最远。问,最大距离是多少?

    思路:我们可以把树上的这个分成两个集合,然后两边的点的数目相等。符合这个条件的就是树的重心,所以我们只需要找到树的中心就行啦。

    //看看会不会爆int!数组会不会少了一维!
    //取物问题一定要小心先手胜利的条件
    #include <bits/stdc++.h>
    using namespace std;
    #pragma comment(linker,"/STACK:102400000,102400000")
    #define LL long long
    #define ALL(a) a.begin(), a.end()
    #define pb push_back
    #define mk make_pair
    #define fi first
    #define se second
    #define haha printf("haha
    ")
    /*
    题目大意:
    给你一棵树,给你k个树上的点对。找到k/2个点对,使它在树上的距离最远。
    问,最大距离是多少?
    */
    const int maxn = 200000 + 5;
    int n, k;
    vector<int> G[maxn];
    bool vis[maxn];
    int dp_cnt[maxn];
    
    int dfs_cnt(int u, int fa){
        int cnt = vis[u];
        for (int i = 0; i < G[u].size(); i++){
            int v = G[u][i];
            if (v == fa) continue;
            cnt += dfs_cnt(v, u);
        }
        return dp_cnt[u] = cnt;
    }
    
    void dfs_ce(int u, int fa, int &ce, int &maxcnt, int treesize){
        int tmp = treesize - dp_cnt[u];
        for (int i = 0; i < G[u].size(); i++){
            int v = G[u][i];
            if (v == fa) continue;
            tmp = max(tmp, dp_cnt[v]);
            dfs_ce(v, u, ce, maxcnt, treesize);
        }
        if (maxcnt > tmp){
            maxcnt = tmp; ce = u;
        }
    }
    
    LL ans;
    void dfs(int u, int fa, int len){
        if (vis[u]) {
            ans = 1LL * len + ans;
        }
        for (int i = 0; i < G[u].size(); i++){
            int v = G[u][i];
            if (v == fa) continue;
            dfs(v, u, len + 1);
        }
    }
    
    int main(){
        scanf("%d%d", &n, &k);
        for (int i = 1; i <= k * 2; i++){
            int u; scanf("%d", &u);
            vis[u] = true;
        }
        for (int i = 1; i < n; i++){
            int u, v; scanf("%d%d", &u, &v);
            G[u].pb(v); G[v].pb(u);
        }
        int treesize = dfs_cnt(1, -1);
        int cetroid, maxcnt = maxn;
        dfs_ce(1, -1, cetroid, maxcnt, treesize);
        dfs(cetroid, -1, 0);
        printf("%lld
    ", ans);
        return 0;
    }
    View Code

    关键:寻找题目关键问题所在(分成两个数目相同的点集),然后探究树的性质

    二:http://codeforces.com/contest/734/problem/E

    题目大意:给你一棵树,树上每个点都是黑色或者是白色,每次有一个操作,选取一个点,把周围和它相邻的所有点的颜色都翻转一次,问最少需要几次操作才能让这棵树变成同一种颜色?

    思路:我刚开始以为就是单纯的树形dp的,于是我刚开始定义dp(i,j)表示i下面的所有子树都变成颜色j需要的最少操作次数。然而发现状态转移的时候完全转移不了。然后表示虽然想到了缩点,但是感觉我这个dp定义的没有什么问题呀,然后就死在这里了,2333

    看了一下官方题解,官方题解上面说:缩点以后,我们可以发现,每次操作以后再缩点,至少可以让反转以后再缩点的树和之前的树相比,结点数至少少了2.

    然后我们再次发现,缩点所需要的最多次数,一定是直径上面点的个数。然后我们发现缩点的次数最少操作次数一定是>=(d+1)/2的,所以就可以很轻松的用两次dfs遍历找到直径了。 

    //看看会不会爆int!数组会不会少了一维!
    //取物问题一定要小心先手胜利的条件
    #include <bits/stdc++.h>
    using namespace std;
    #pragma comment(linker,"/STACK:102400000,102400000")
    #define LL long long
    #define ALL(a) a.begin(), a.end()
    #define pb push_back
    #define mk make_pair
    #define fi first
    #define se second
    #define haha printf("haha
    ")
    const int maxn = 2e5 + 10;
    int n, pos, maxdeep, ans;
    vector<int> G[maxn];
    int color[maxn], dp[maxn];
    
    int dfs(int u, int fa, int deep){
        if (deep > maxdeep){
            maxdeep = deep; pos = u;
        }
        for (int i = 0; i < G[u].size(); i++){
            int v = G[u][i];
            if (v == fa) continue;
            dfs(v, u, deep + (color[u] != color[v]));
        }
    }
    
    void dfs_dia(int u, int fa, int deep){
        if (deep > ans) ans = deep;
        for (int i = 0; i < G[u].size(); i++){
            int v = G[u][i];
            if (v == fa) continue;
            dfs_dia(v, u, deep + (color[u] != color[v]));
        }
    }
    
    int main(){
        cin >> n;
        for (int i = 1; i <= n; i++) scanf("%d", color + i);
        for (int i = 1; i < n; i++){
            int u, v; scanf("%d%d", &u, &v);
            G[u].pb(v), G[v].pb(u);
        }
        dfs(1, -1, 0);
        dfs_dia(pos, -1, 0);
        printf("%d
    ", (ans + 1) / 2);
        return 0;
    }
    View Code

    三:zstu oj 4248   链接:戳这里

    题目大意:给你一棵以1位根的,边有权值的树,权值定为cost,每个点也有一个val。定义dis(i,j)表示i~j的所有路径权值和。如果存在dis(i,j) < val(i)-val(j),那么所有j的子树都被减去。问最后还有多少个节点?

    思路:

    path(u)指从根到u这个节点的边权和,

    dis(u,v) = path(v)-path(u) < val(u)-val(v)

    val(v)<val(u)+path(u)-Path(v)

    所以我们从根开始搜只要一路维护一个max{val(u)+path(u)}即可

    关键:公式转化,讲道理应该很快要想到O(n)的方法,然后这个公式一定是和前面传下来的数值有关的(唉,我好菜啊)

    //看看会不会爆int!数组会不会少了一维!
    //取物问题一定要小心先手胜利的条件
    #include <bits/stdc++.h>
    using namespace std;
    #pragma comment(linker,"/STACK:102400000,102400000")
    #define LL long long
    #define ALL(a) a.begin(), a.end()
    #define pb push_back
    #define mk make_pair
    #define fi first
    #define se second
    #define haha printf("haha
    ")
    const int maxn = 1e5 + 5;
    struct Node{
        int to; LL cost;
        Node(int to = 0, LL cost = 0): to(to), cost(cost){}
    };
    vector<Node> G[maxn];
    int dp[maxn];
    LL val[maxn], path[maxn];
    int n;
    
    int dfs_cnt(int u, int fa, LL p){
        dp[u] = 1, path[u] = p;
        for (int i = 0; i < G[u].size(); i++){
            int v = G[u][i].to;
            if (v == fa) continue;
            dp[u] += dfs_cnt(v, u, p + G[u][i].cost);
        }
        return dp[u];
    }
    
    int dfs(int u, int fa, LL maxval){
        int cnt = 0;
        for (int i = 0; i < G[u].size(); i++){
            int v = G[u][i].to;
            if (v == fa) continue;
            if (maxval > path[v] + val[v]) cnt += dp[v];
            else cnt += dfs(v, u, path[v] + val[v]);
        }
        return cnt;
    }
    
    int main(){
        int t; cin >> t;
        while (t--){
            memset(path, 0, sizeof(path));
            scanf("%d", &n);
            for (int i = 1; i <= n; i++) G[i].clear();
            for (int i = 1; i < n; i++){
                int u, v; LL c;
                scanf("%d%d%lld", &u, &v, &c);
                G[u].pb(Node(v, c)); G[v].pb(Node(u, c));
            }
            for (int i = 1; i <= n; i++)
                scanf("%lld", val + i);
            dfs_cnt(1, -1, 0);
            int ans = n - dfs(1, -1, val[1] + path[1]);
            printf("%d
    ", ans);
        }
        return 0;
    }
    View Code

    四: 链接:戳这里

    题目大意:给你一棵有n个节点的树,根是1,每次我们都从1出发,并且走到他的儿子的可能性都是随机的。每一个儿子的权值是根据走的不同的路径来计算的,走的路径不同,儿子的权值就不同(这一点用cf里面的那个代码来表示) 

    let starting_time be an array of length n
    current_time = 0
    dfs(v):
    current_time = current_time + 1
    starting_time[v] = current_time
    shuffle children[v] randomly (each permutation with equal possibility)
    // children[v] is vector of children cities of city v
    for u in children[v]:
    dfs(u)

    问,最后请计算每一个节点位置的权值。

    思路:

    其实单单的看到这道题我是很害怕的,因为我很害怕这种求什么期望啊这类的问题。但是感觉概率论这门课上了以后,感觉对数学期望有了一个新的认识,貌似不是那么怕了,然后推导了一下式子以后发现,这道题并没有和我想象中一样那么难。

    首先,我们计算出每个节点,他下面的子节点的个数,然后我是列出了样例一中的②、④、⑥、③这五个节点的数学期望的计算方式。然后我们可以得到一个数学期望的公式

    目前节点的数学期望 = 父亲节点的数学期望+1+(父亲节点所有孩子的节点和 - 目前节点的size)/2.

    该公式来的过程如下:

    假定父亲节点是fa,父亲节点下面儿子的个数为m,父亲节点的下面的晚辈的总个数(包括自身)是fasize,儿子节点为child,儿子节点的下面晚辈的总个数(包括自身)是childsize,儿子节点下面的孙子的个数为n。

    然后目前我们可以发现,当前我们停留的点为child,那么,child到fa这条路经过的次数一定是2^(n-1)次,所以,除了目前这个child外,其他儿子节点每个的贡献次数都为2^n-2次,所以我们得到如下的递推式:

    父亲节点的期望值+(2^(n-1) + (其他儿子的size和) * 2^(n-2)) / (2^(n-1)),因此就得到上面的递推式啦

    //看看会不会爆int!数组会不会少了一维!
    //取物问题一定要小心先手胜利的条件
    #include <bits/stdc++.h>
    using namespace std;
    #pragma comment(linker,"/STACK:102400000,102400000")
    #define LL long long
    #define ALL(a) a.begin(), a.end()
    #define pb push_back
    #define mk make_pair
    #define fi first
    #define se second
    #define haha printf("haha
    ")
    const int maxn = 1e5 + 5;
    vector<int> road[maxn];
    int n;
    LL tree[maxn];///包括当前节点
    double res[maxn];
    
    LL dfs_size(int u){
        tree[u] = 1;
        for (int i = 0; i < road[u].size(); i++){
            tree[u] += dfs_size(road[u][i]);
        }
        return tree[u];
    }
    
    void dfs_ans(int u, int fa){
        res[u] = 1.0 + res[fa];
        if (fa != 0) res[u] += 1.0 * (tree[fa] - 1 - tree[u]) / 2.0;
        for (int i = 0; i < road[u].size(); i++)
            dfs_ans(road[u][i], u);
    }
    
    int main(){
        cin >> n;
        for (int i = 2; i <= n; i++){
            int x; scanf("%d", &x);
            road[x].push_back(i);
        }
        dfs_size(1);
        dfs_ans(1, 0);
        for (int i = 1; i <= n; i++){
            printf("%.6f
    ", res[i]);
        }
        return 0;
    }
    View Code

    关键:熟悉数学期望,掌握排列组合

    五:

    六:

    七:

    八:

    九:

    十:

  • 相关阅读:
    JAVA中final关键字的作用
    JAVA作用域和排序算法介绍
    JAVA的控制结构
    Java概述
    P6329 【模板】点分树 | 震波[点分树]
    BZOJ #4771. 七彩树 [主席树,链并,差分]
    CF1276F Asterisk Substrings [后缀自动机]
    P4173 残缺的字符串 [FFT]
    CF528D Fuzzy Search [FFT]
    #4589. Hard Nim [FWT]
  • 原文地址:https://www.cnblogs.com/heimao5027/p/6043774.html
Copyright © 2011-2022 走看看