• 树形dp小结


    最近开了一个树形专题,但是很多题笨比mwh都是看了题解才会的,所以自己掌握的并不好,打算做一些小的总结。会持续更新。

    1.hdu2196 Computer

    原题链接 http://acm.hdu.edu.cn/showproblem.php?pid=2196

    题意:给出一棵树,求离每个节点最远的点的距离

    是一道非常经典的树形dp入门题。

    首先选定一个结点为树跟把这棵树转化为有根树,考虑到每个结点所能达到的最远距离一定是到它子节点的最大距离加上经过其父节点的最大距离,所以我们只需在dfs过程中维护这两个值就可以在

    最后O(n)得到答案。它到子节点的距离很好维护,关键是经过父节点的最长距离有两种情况,第一种录井是他的父节点经过他的父节点的父节点的距离,第二种是经过他的父节点再到他父节点的其他

    子节点的路径。第二种情况需要考虑他的父节点到子树的最长距离是否就经过这个结点。为了判断,我们需要再维护一个变量,就是当前结点到子树的次长距离,这道题差不多就这样了。

    (害,感觉自己表达能力真的很成问题)

    #include <stdio.h>
    #include <iostream>
    #include <cstring>
    #include <algorithm>
    #include <cmath>
    #include <queue>
    #include <map> 
    #include <stack>
    #include <sstream>
    #include <set>
    #pragma GCC optimize(2)
    
    //#define int long long
    #define mm(i,v) memset(i,v,sizeof i);
    #define mp(a, b) make_pair(a, b)
    #define pi acos(-1)
    #define fi first
    #define se second
    //你冷静一点,确认思路再敲!!! 
    
    using namespace std;
    typedef long long ll;
    
    const int N = 2e5 + 5, mod = 1e9 + 9, INF = 0x3f3f3f3f;
    ll n, idx;
    ll e[N], ne[N], h[N], w[N], ls[N], da[N];
    ll dp[5][N];
    
    inline ll read(){
        char c=getchar();ll x=0,f=1;
        while(c<'0'||c>'9'){if(c=='-')f=-1; c=getchar();}
        while(c>='0'&&c<='9'){x=x*10+c-'0'; c=getchar();}
        return x*f;
    }
    
    void add(ll a, ll b, ll c) {
        e[idx] = b;
        w[idx] = c;
        ne[idx] = h[a];
        h[a] = idx++;
    }
    
    void dfs(ll u, ll fa) {
        for (int i = h[u]; ~i; i = ne[i]) {
            ll v = e[i], z = w[i];
            if (v == fa) continue;
            dfs(v, u);
            if (dp[0][v] + z > dp[0][u]) {
                dp[1][u] = dp[0][u];
                dp[0][u] = dp[0][v] + z;
                ls[u] = v;
            } else if (dp[0][v] + z > dp[1][u])
                dp[1][u] = dp[0][v] + z;
        }
    }
    
    void dfs2(ll u, ll fa) {
        for (int i = h[u]; ~i; i = ne[i]) {
            ll v = e[i], z = w[i];
            if (v == fa) continue;
            if (ls[u] == v) dp[2][v] = max(dp[2][u], dp[1][u]) + z;
            else dp[2][v] = max(dp[2][u], dp[0][u]) + z;
            dfs2(v, u);
        }
    }
    
    void init() {
        idx = 0;
        mm(h, -1);
        mm(dp, 0);
    }
    
    int main()
    {
        while (cin >> n) {
            init();
            for (int i = 2; i <= n; ++i) {
                int x, y;
                x = read(); y = read();
                add(i, x, y);
                add(x, i, y);
            }
            dfs(1, -1);
            dfs2(1, -1);
            for(int i = 1; i <= n; i++) 
                printf("%lld
    ", max(dp[0][i], dp[2][i]));
        }   
    }
    /*
    5
    1 3
    1 4
    1 5
    1 7
    
    7
    10
    11
    12
    12
    */
    View Code

    2.CF 219D. Choosing Capital for Treeland

    原题链接: https://codeforces.com/problemset/problem/219/D

    题意:给出一个有向树,你可以反转边的方向,找到满足到所有点所需反转边数最少的点。输出反转的次数和所有满足满足的点。

    很容易就可以想到对于这棵树,我们把存在的边的边权设为0,然后增加其反边权设为1。问题就转换成了求点到所有别的点的耗费最小。

    不妨假设1为改树的根节点,进行一遍dfs得到每个结点到其子树的耗费。用d维护。

    然后进行第二次dfs,这次dfs我们从根节点开始往子节点更新,d表示每个点到其余结点的距离。对于每个u和v如果u—>v权为1,那么d[v] = d[u] - 1,否则d[v] = d[u] + 1

    (感觉这题思路还是很巧妙的)

    #include <stdio.h>
    #include <iostream>
    #include <cstring>
    #include <algorithm>
    #include <cmath>
    #include <queue>
    #include <map> 
    #include <stack>
    #include <sstream>
    #include <set>
    #pragma GCC optimize(2)
    
    //#define int long long
    #define mm(i,v) memset(i,v,sizeof i);
    #define mp(a, b) make_pair(a, b)
    #define pi acos(-1)
    #define fi first
    #define se second
    //你冷静一点,确认思路再敲!!! 
    
    using namespace std;
    typedef long long ll;
    typedef pair<int, int > PII;
    priority_queue< PII, vector<PII>, greater<PII> > que;
    stringstream ssin; //  ssin << string   while ( ssin >> int)
    
    const int N = 4e5 + 5, mod = 1e9 + 9, INF = 0x3f3f3f3f;
    int n, idx, cnt, ans;
    int e[N], h[N], ne[N], Size[N], d[N], w[N];
    int dp[N], q[N];
    
    void add(int a, int b, int c) {
        w[idx] = c;
        e[idx] = b;
        ne[idx] = h[a];
        h[a] = idx++;
    }
    
    inline int read(){
        char c=getchar();int x=0,f=1;
        while(c<'0'||c>'9'){if(c=='-')f=-1; c=getchar();}
        while(c>='0'&&c<='9'){x=x*10+c-'0'; c=getchar();}
        return x*f;
    }
    
    void dfs1(int u, int fa) {
        for (int i = h[u]; ~i; i = ne[i]) {
            int v = e[i], z = w[i];
            if (v == fa) continue;
            dfs1(v, u);
            dp[u] += dp[v] + z;
        }
    }
    
    void dfs2(int u, int fa) {
        for (int i = h[u]; ~i; i = ne[i]) {
            int v = e[i], z = w[i];
            if (v == fa) continue;
            if (z == 1) dp[v] = dp[u] - 1;
            else dp[v] = dp[u] + 1;
            dfs2(v, u);
        }
        if (dp[u] < ans) {
            ans = dp[u];
            cnt = 0;
        }
        if (dp[u] == ans) {
            q[++cnt] = u;
        }
    }
    int main()
    {
        mm(h, -1);
        cin >> n;
        for (int i = 1; i < n; ++i) {
            int a, b;
            a = read();
            b = read();
            add(a, b, 0);
            add(b, a, 1);
        }
        ans = INF;
        dfs1(1, -1);
        dfs2(1, -1);
        cout << ans << endl;
        sort(q + 1, q + 1 + cnt);
        for (int i = 1; i <= cnt; ++i) {
            cout << q[i] << " ";
        }
        puts("");
        // system("pause");
        return 0;
    }
    View Code

    3.POJ1741 Tree (树形dp+点分治)

    原题链接:http://poj.org/problem?id=1741

    (为什么树形dp的专题里混进了一个奇怪的东西???)

    题意:有一棵树,每条边多有一个边权,问你有多少对(u,v)满足dis(u,v)<= k

     树分治的板子题,我自己讲不清楚,也没有熟练掌握,网上找了份题解

    题解

    树的点分治模板题,第一次写

    考虑到路径只有两种情况,一是经过根节点,二是不经过根节点。

    如果不经过根节点,那么一定经过最小公共子树的根节点,可以转化为问题一的子问题。

    于是考虑怎么递归解决问题一。

    对于根节点进行一次dfs,求出deep,并将其从小到大排序。

    避免重复,只需要求出其中deep[x]≤deep[y]且deep[x]+deep[y]≤m的个数。

    用i表示左指针,j表示右指针,i从左向右遍历。

    如果deep[i]+deep[j]≤m,则点对(i,t)(i<t≤j)都符合题意,将j-i加入答案中,并且i++;否则j--。

    然而这样还会重复计算在同一棵子树中的点对,所以再进行下一步dfs之前需要减去重复部分。

    但是这样做会TLE。为什么?因为树可能会退化,导致选择链头时时间复杂度极大。

    于是每次不能固定选择root,而是以重心作为root去处理,这样能保证时间复杂度再O(nlog2n)以下。

     自己的代码

    #include <stdio.h>
    #include <iostream>
    #include <cstring>
    #include <algorithm>
    #include <cmath>
    #include <queue>
    #include <map> 
    #include <stack>
    #include <sstream>
    #include <set>
    #pragma GCC optimize(2)
    
    //#define int long long
    #define mm(i,v) memset(i,v,sizeof i);
    #define mp(a, b) make_pair(a, b)
    #define pi acos(-1)
    #define fi first
    #define se second
    //你冷静一点,确认思路再敲!!! 
    
    using namespace std;
    typedef long long ll;
    typedef pair<int, int > PII;
    priority_queue< PII, vector<PII>, greater<PII> > que;
    stringstream ssin; //  ssin << string   while ( ssin >> int)
    
    const int N = 2e4 + 5, mod = 1e9 + 9, INF = 0x3f3f3f3f;
    int n, k, idx, sum, focus, ans, allnode, Min, id;
    int h[N], e[N], ne[N], w[N], dp[N], Size[N], dist[N], vis[N], deep[N];
    
    inline int read(){
        char c=getchar();int x=0,f=1;
        while(c<'0'||c>'9'){if(c=='-')f=-1; c=getchar();}
        while(c>='0'&&c<='9'){x=x*10+c-'0'; c=getchar();}
        return x*f;
    }
    
    void init() {
        mm(h, -1);
        idx = 0;
        sum = 0;
        mm(dp, 0);
        mm(dist, 0);
        mm(Size, 0);
        mm(vis, 0);
    }
    
    void add(int a, int b, int c) {
        e[idx] = b;
        w[idx] = c;
        ne[idx] = h[a];
        h[a] = idx++;
    }
    
    void getfocus(int u, int fa) {  // 求树的重心
        Size[u] = 1;
        dp[u] = 0;
        for (int i = h[u]; ~i; i = ne[i]) {
            int v = e[i];
            if (v == fa || vis[v]) continue;
            getfocus(v, u);
            Size[u] += Size[v];
            dp[u] = max(dp[u], Size[v]);
        }
        dp[u] = max(dp[u], allnode - Size[u]);
        if (Min > dp[u]) {
            Min = dp[u];
            focus = u;
        }
    }
    
    void dfs(int u, int fa) {
        deep[++id] = dist[u];
        for (int i = h[u]; ~i; i = ne[i]) {
            int v = e[i], z = w[i];
            if (v == fa || vis[v]) continue;
            dist[v] = dist[u] + z;
            dfs(v, u);
        }
    }
    
    int cal(int x, int now) {
        dist[x] = now, id = 0;
        dfs(x, -1);
        sort(deep + 1, deep + 1 + id);
        int ans = 0;
        for (int l = 1, r = id; l < r; ) {
            if (deep[l] + deep[r] <= k) {
                ans += r - l;
                l++;
            } else {
                r--;
            }
        }
        return ans;
    }
    
    void solve(int x) {
        vis[x] = 1;
        ans += cal(x, 0);
        for (int i = h[x]; ~i; i = ne[i]) {
            int v = e[i], z = w[i];
            if (vis[v]) continue;
            ans -= cal(v, z);
            allnode = Size[v];
            focus = 0, Min = 1e9;
            getfocus(v, x);
            solve(focus);
        }
    }
    
    int main()
    {
        while (cin >> n >> k) {
            init();
            if (n == 0 && k == 0) break;
            for (int i = 1; i <= n - 1; ++i) {
                int x, y, z;
                x = read();
                y = read();
                z = read();
                add(x, y, z);
                add(y, x, z);
            }
            ans = 0;
            focus = 0; // 树的重心
            allnode = n, Min = 1e9;
            getfocus(1, -1);
            solve(focus);
            cout << ans << endl;
        }
        // system("pause");
        return 0;
    }
    View Code

    好累啊今天就到这了

    2020-07-14 15:12:54

  • 相关阅读:
    Java 中的四种权限修饰符
    Java final关键词
    Java 多态
    Java 接口
    Java 抽象类
    iOS下JS与OC互相调用(八)--Cordova详解+实战
    iOS下JS与OC互相调用(七)--Cordova 基础
    计算机网络中的TCP/IP模型
    计算机网络中的七层模型
    Android简易实战教程--第三十一话《自定义土司》
  • 原文地址:https://www.cnblogs.com/mwh123/p/13299149.html
Copyright © 2020-2023  润新知