• 牛客题单_动态规划课程树型dp例题


    NC15033 小G有一个大树

    大意:

    给出一个数,求平衡点。

    平衡点的定义是,删掉这个点之后,剩下的最大子树节点数最小

    思路:

    #include <bits/stdc++.h>
    
    using namespace std;
    
    const int N = 1e6 + 5;
    typedef long long LL;
    int n;
    vector<int> mp[N];
    int dp[N];
    int pos = 0x3f3f3f3f, maxn = 0x3f3f3f3f;
    void dfs(int now, int fa) {
        dp[now] = 1;
        int tmp = -0x3f3f3f3f;
        for (int i = 0; i < mp[now].size(); i++) {
            int ne = mp[now][i];
            if (ne == fa) continue;
            dfs(ne, now);
            dp[now] += dp[ne];
            tmp = max(tmp, dp[ne]);
        }
        tmp = max(tmp, n - dp[now]);
        if (tmp < maxn) {
            pos = now, maxn = tmp;
        } else if (tmp == maxn) {
            if (pos > now) pos = now;
        }
    }
    
    int main() {
        cin >> n;
        for (int i = 0; i < n - 1; i++) {
            int x, y;
            cin >> x >> y;
            mp[x].push_back(y), mp[y].push_back(x);
        }
        dfs(1, 0);
        cout << pos << ' ' << maxn << endl;
        return 0;
    }
    

    NC22598 Rinne Loves Edges

    大意:

    给出n个点的树,以及一个 “重要点” S,现在要求选择性删除一些边,使得原图中所有除 S 之叶子节点都不能到达 S。

    定义删除一条边的代价为这条边的边权,求最小代价

    思路:

    因为是删掉一个树,所以dp[i]代表删掉以i为根的子树中全部叶子节点的最小花费

    那么只需要判断是将i的出边全部删掉,还是只删掉i的入边即可

    #include <bits/stdc++.h>
    
    using namespace std;
    
    const int N = 1e6 + 5;
    typedef long long LL;
    int n, m, s;
    vector<pair<int, int> > mp[N];
    int dp[N];
    void dfs(int now, int fa, int pre) {
        dp[now] = 0;
        for (int i = 0; i < mp[now].size(); i++) {
            int ne = mp[now][i].first, w = mp[now][i].second;
            if (ne == fa) continue;
            dfs(ne, now, w);
            dp[now] += dp[ne];
        }
        if (mp[now].size() == 1) dp[now] = pre;
        else dp[now] = min(dp[now], pre);
    }
    
    int main() {
        cin >> n >> m >> s;
        for (int i = 0; i < m; i++) {
            int x, y, w;
            cin >> x >> y >> w;
            mp[x].push_back({y, w});
            mp[y].push_back({x, w});
        }
        for (int i = 0; i < mp[s].size(); i++) {
            int ne = mp[s][i].first, w = mp[s][i].second;
            dfs(ne, s, w);
            dp[s] += dp[ne];
        }
        cout << dp[s] << endl;
        return 0;
    }
    

    NC24953 Cell Phone Network

    大意:

    求点的最小点覆盖裸题

    思路:

    dp[i][0]:以i为根节点的子树,第i个结点的父结点被选的最小花费
    dp[i][1]:以i为根节点的子树,第i个结点有一个子节点被选的最小花费
    dp[i][2]:以i为根节点的子树,第i个节点本身被选的最小花费
    
    #include <bits/stdc++.h>
    
    using namespace std;
    
    const int N = 1e6 + 5;
    typedef long long LL;
    int n, dp[N][3];
    vector<int> mp[N];
    
    void dfs(int now, int fa) {
        dp[now][2] = 1;
        int sum = 0;
        for (int i = 0; i < mp[now].size(); i++) {
            int ne = mp[now][i];
            if (ne == fa) continue;
            dfs(ne, now);
            dp[now][0] += min(dp[ne][1], dp[ne][2]);  //当前点由父节点覆盖
            dp[now][2] +=
                min({dp[ne][0], dp[ne][1], dp[ne][2]});  //当前点由自身覆盖
            sum += min(dp[ne][1] , dp[ne][2]);//子节点被覆盖的花费
        }
        dp[now][1] = 0x3f3f3f3f;
        for (int i = 0; i < mp[now].size(); i++) {
            int ne = mp[now][i];
            if (ne == fa) continue;
            dp[now][1]=min(dp[now][1],sum - min(dp[ne][1], dp[ne][2]) + dp[ne][2]);
        }
    }
    
    int main() {
        cin >> n;
        for (int i = 0; i < n - 1; i++) {
            int x, y;
            cin >> x >> y;
            mp[x].push_back(y);
            mp[y].push_back(x);
        }
        dfs(1, 0);
        cout << min(dp[1][1], dp[1][2]);
        return 0;
    }
    

    NC50505 二叉苹果树

    大意: 有一棵二叉苹果树,如果树枝有分叉,一定是分两叉,即没有只有一个儿子的点。这棵树共 N 个节点,编号为 1 至 N,树根编号一定为 1。我们用一根树枝两端连接的节点编号描述一根树枝的位置。一棵苹果树的树枝太多了,需要剪枝。但是一些树枝上长有苹果,给定需要保留的树枝数量,求最多能留住多少苹果。这里的保留是指最终与1号点连通。1 ≤ Q < N ≤ 100.N≠1,
    题解: 本题是分组背包的变形,本题问的是保留m条边可以得到的最大的苹果树,而分析树的性质我们可以知道,如果想要保留子树,那么子树的父节点必须保留,那么这题就变成了一道有依赖的背包问题,背包体积为要保留的树边+1(把边转化为点),然后把每条边的权值转移到对应的子节点上去

    #include <bits/stdc++.h>
    
    using namespace std;
    
    const int N = 1e2 + 5;
    typedef long long LL;
    int n, q, dp[N][N];
    //dp[i][j]代表以i为根的子树保留j个节点的最大权值
    vector<pair<int, int> > mp[N];
    void dfs(int now, int fa, int pre) {
        for (int i = 0; i < mp[now].size(); i++) {
            int ne = mp[now][i].first, w = mp[now][i].second;
            if (ne == fa) continue;
            dfs(ne, now, w);
            for (int j = q - 1; j >= 0; j--) {//必须要选根节点,所以容量变为q-1
                for (int k = 0; k <= j; k++) {
                    dp[now][j] = max(dp[now][j], dp[now][j - k] + dp[ne][k]);
                }
            }
        }
        for (int i = q; i >= 1; i--) {
            dp[now][i] = dp[now][i - 1] + pre; //选上这个根节点
        }
        dp[now][0] = 0;
    }
    
    int main() {
        cin >> n >> q;
        for (int i = 0; i < n - 1; i++) {
            int x, y, w;
            cin >> x >> y >> w;
            mp[x].push_back({y, w});
        }
        q++;//q要+1,因为q个边要保留q+1个点
        dfs(1, 0, 0);
        cout << dp[1][q] << endl;
        return 0;
    }
    

    NC51178 没有上司的舞会

    最大点独立集裸题

    #include <bits/stdc++.h>
    
    using namespace std;
    
    const int N = 1e6 + 5;
    typedef long long LL;
    int n, w[N],dp[N][2];
    vector<int> mp[N];
    
    void dfs(int now, int fa) {
        dp[now][1] = w[now];
        for (int i = 0; i < mp[now].size(); i++) {
            int ne = mp[now][i];
            if (ne == fa) continue;
            dfs(ne, now);
            dp[now][0] += max(dp[ne][0], dp[ne][1]);
            dp[now][1] += dp[ne][0];
        }
    }
    
    int main() {
        cin >> n;
        for (int i = 1; i <= n; i++) cin >> w[i];
        for (int i = 1; i <= n - 1; i++) {
            int x, y;
            cin >> x >> y;
            mp[x].push_back(y), mp[y].push_back(x);
        }
        dfs(1, 0);
        cout << max(dp[1][0], dp[1][1]);
        return 0;
    }
    

    POJ1463 Strategic game

    求边的最小点覆盖裸题,和点的最小点覆盖不同的是,这个题要求每个边至少选一个点

    对于一个点来说,如果不选它,那么需要选全部的子节点,如果选他,那么子节点可选可不选

    #include <iostream>
    #include <cstring>
    #include <algorithm>
    #include <cstdio>
    #include <vector>
    #include <queue>
    #include <string>
    using namespace std;
    
    const int N = 1e6 + 5;
    typedef long long LL;
    int n, dp[N][2];
    vector<int> mp[N];
    
    void dfs(int now, int fa) {
        dp[now][1] = 1;
        int sum = 0;
        for (int i = 0; i < mp[now].size(); i++) {
            int ne = mp[now][i];
            if (ne == fa) continue;
            dfs(ne, now);
            dp[now][1] += min(dp[ne][1], dp[ne][0]);
            dp[now][0] += dp[ne][1];
        }
    }
    
    int main() {
        while (scanf("%d", &n) != EOF) {
            memset(dp, 0, sizeof dp);
            for (int i = 0; i < n; i++) mp[i].clear();
            for (int i = 0; i < n; i++) {
                int x, num;
                scanf("%d:(%d)", &x, &num);
                for (int j = 0; j < num; j++) {
                    int y;
                    scanf("%d", &y);
                    mp[x].push_back(y);
                    mp[y].push_back(x);
                }
            }
            dfs(0, -1);
            cout << min(dp[0][0], dp[0][1]) << endl;
        }
        return 0;
    }
    

    NC202475 树上子链

    大意:

    给定一棵树 T ,树 T 上每个点都有一个权值(有正有负)。

    定义一颗树的子链的大小为:这个子链上所有结点的权值和 。

    请在树 T 中找出一条最大的子链并输出。

    思路:

    如果想AC这道题的话,直接dp出每个点的最大次大子链,就行了,因为最后答案一定是某个点的最大次大之和

    假如我们换一下题目,要求输出树上每个点包含这个点的最大链,这就必须换根了

    换根写法:

    #include <bits/stdc++.h>
    
    using namespace std;
    
    const int N = 1e6 + 5;
    typedef long long LL;
    int n;
    vector<int> mp[N];
    LL dp[N][3], w[N],res[N];
    void dfs1(int now, int fa) {
        dp[now][0]=dp[now][1]=-0x3f3f3f3f3f3f3f3f;
        for (int i = 0; i < mp[now].size(); i++) {
            int ne = mp[now][i];
            if (ne == fa) continue;
            dfs1(ne, now);
            if(dp[now][0]<dp[ne][0]){
                dp[now][1] = dp[now][0];
                dp[now][0] = dp[ne][0];
            }
            else if(dp[now][1]<dp[ne][0]){
                dp[now][1] = dp[ne][0];
            }
        }
        dp[now][0] = max(0ll, dp[now][0]) + w[now];
        dp[now][1] = max(0ll, dp[now][1]) + w[now];
    }
    
    void dfs2(int now, int fa) {
        for (int i = 0; i < mp[now].size(); i++) {
            int ne = mp[now][i];
            if (ne == fa) continue;
            if(dp[ne][0]+w[now]==dp[now][0]){
                dp[ne][2] =max({0ll,dp[now][1],dp[now][2]})+w[ne];
            }
            else {
                dp[ne][2] =max({0ll,dp[now][0],dp[now][2]})+w[ne];
            }
            dfs2(ne, now);
        }
        res[now] = dp[now][0] + max(dp[now][2], dp[now][1])-w[now];
    }
    
    int main() {
        cin >> n;
        for (int i = 1; i <= n; i++) {
            cin >> w[i];
        }
        for (int i = 0; i < n - 1; i++) {
            int x, y;
            cin >> x >> y;
            mp[x].push_back(y), mp[y].push_back(x);
        }
        dfs1(1, 0);
        dp[1][2] = w[1];
        dfs2(1, 0);
        LL ans =-0x3f3f3f3f3f3f3f3f;
        for (int i = 1; i <= n; i++)ans= max(ans,res[i]);
        cout << ans << endl;
        return 0;
    }
    

    一次dp写法:

    #include <bits/stdc++.h>
    
    using namespace std;
    
    const int N = 1e6 + 5;
    typedef long long LL;
    int n;
    vector<int> mp[N];
    LL dp[N][2], w[N],res[N];
    void dfs1(int now, int fa) {
        dp[now][0]=dp[now][1]=-0x3f3f3f3f3f3f3f3f;
        for (int i = 0; i < mp[now].size(); i++) {
            int ne = mp[now][i];
            if (ne == fa) continue;
            dfs1(ne, now);
            if(dp[now][0]<dp[ne][0]){
                dp[now][1] = dp[now][0];
                dp[now][0] = dp[ne][0];
            }
            else if(dp[now][1]<dp[ne][0]){
                dp[now][1] = dp[ne][0];
            }
        }
        dp[now][0] = max(0ll, dp[now][0]) + w[now];
        dp[now][1] = max(0ll, dp[now][1]) + w[now];
    }
    
    int main() {
        cin >> n;
        for (int i = 1; i <= n; i++) {
            cin >> w[i];
        }
        for (int i = 0; i < n - 1; i++) {
            int x, y;
            cin >> x >> y;
            mp[x].push_back(y), mp[y].push_back(x);
        }
        dfs1(1, 0);
        LL ans =-0x3f3f3f3f3f3f3f3f;
        for (int i = 1; i <= n; i++)ans= max(ans,dp[i][0]+dp[i][1]-w[i]);
        cout << ans << endl;
        return 0;
    }
    

    NC210473 吉吉王国

    大意:

    给出n和m,代表一个n个节点的树,现在要求删掉一些边,使得所有原来的叶子节点都和1号点不连通,且删掉边的总权值不超过m

    问删掉的边中,最小的最大边是多少,如果不能符合要求,输出-1

    思路:

    最大最小值问题要想到二分求解,对于当前二分的mid,做dfs,计算dp[1]

    dp[i]代表删掉所有i的子树,所需要的最小权值,转移方程:

    if (w <= mid) dp[now] += min(w, dp[ne]);
    else dp[now] += dp[ne];
    
    #include <bits/stdc++.h>
    
    using namespace std;
    const int N = 1e3 + 5;
    typedef long long LL;
    #define int LL
    int n, q, dp[N],cnt=0,sum[N];
    vector<pair<int, int> > mp[N];
    void dfs(int now, int fa, int mid) {
        for (int i = 0; i < mp[now].size(); i++) {
            int ne = mp[now][i].first, w = mp[now][i].second;
            if (ne == fa) continue;
            dfs(ne, now, mid);
            if (w <= mid) dp[now] += min(w, dp[ne]);
            else dp[now] += dp[ne];
        }
        if(mp[now].size()==1&&now!=1){
            dp[now] = 0x3f3f3f3f;
        }
    }
    
    bool check(int mid) {
        memset(dp, 0, sizeof dp);
        dfs(1, 0, mid);
        if (dp[1] <= q) return true;
        else return false;
    }
    
    signed main() {
        cin >> n >> q;
        int l = 0x3f3f3f3f, r = 0,maxn;
        for (int i = 0; i < n - 1; i++) {
            int x, y, w;
            cin >> x >> y >> w;
            mp[x].push_back({y, w}),mp[y].push_back({x, w});
            l = min(l, w), r = max(r, w);
        }
        maxn = ++r;
        while (l < r) {
            int mid = (l + r) >> 1;  // 区别:有无加1
            if (check(mid)) r = mid;  //区别
            else l = mid + 1;  //区别
        }
        if (r == maxn) cout << -1 << endl;
        else cout << l << endl;
        return 0;
    }
    
  • 相关阅读:
    iOS-关于博客园博客到简书平台的迁移
    iOS-工作经验+资料分享(长期更新)
    iOS-公司开发者账号的申请和注册(原创+亲身+2016年申请+附带与邓白氏公司的往来邮件截图)
    iOS-HTML-02
    iOS-HTML-01
    iOS-保存照片或者视频到自定义相薄中以及读取数据
    iOS-UITableView的优化(纯手打原创)
    iOS-多线程之GCD(原创)
    iOS-绘图(Quartz2D)的简单使用(原创)
    iOS-Cocoapods
  • 原文地址:https://www.cnblogs.com/dyhaohaoxuexi/p/14447775.html
Copyright © 2020-2023  润新知