acwing算法提高之动态规划--树形DP

123 篇文章 1 订阅

1 基础知识

暂无。。。

2 模板

暂无。。。

3 工程化

题目1:树的最长路径。

解题思路:遍历从根结点到叶子结点的最长距离和次长距离,注意遍历每一个下一步,因此避免了这两个路径有重叠。更新res,即res = max(res, d1 + d2)。返回最长距离d1

C++代码如下,

#include <iostream>
#include <vector>
#include <unordered_map>

using namespace std;

int n;
unordered_map<int, vector<pair<int,int>>> g;

int res = 0;

int dfs(int a, int fa) {
    int d1 = 0;//d1表示最大长度
    int d2 = 0;//d2表示次大长度
    
    for (auto [b,c] : g[a]) {
        if (b == fa) continue;
        int d = dfs(b, a) + c;
        
        if (d >= d1) {
            d2 = d1;
            d1 = d;
        } else if(d > d2) {
            d2 = d;
        }
    }
    
    res = max(res, d1 + d2);
    return d1;
}

int main() {
    cin >> n;
    for (int i = 0; i < n - 1; ++i) {
        int a, b, c;
        cin >> a >> b >> c;
        g[a].emplace_back(b,c);
        g[b].emplace_back(a,c);
    }
    
    dfs(1, -1);
    
    cout << res << endl;
    
    return 0;
}

题目2:树的中心。

解题思路:暴力解法,遍历每一个结点,以该结点为根结点,计算到叶子结点的最长路径。

C++代码如下,

#include <iostream>
#include <vector>
#include <unordered_map>

using namespace std;

int n;
unordered_map<int,vector<pair<int,int>>> g;

int dfs(int a, int fa) {
    int d = 0;
    for (auto [b, c] : g[a]) {
        if (b == fa) continue;
        d = max(d, dfs(b, a) + c);
    }
    return d;
}

int main() {
    cin >> n;
    for (int i = 0; i < n - 1; ++i) {
        int a, b, c;
        cin >> a >> b >> c;
        g[a].emplace_back(b,c);
        g[b].emplace_back(a,c);
    }
    
    int res = 0x3f3f3f3f;
    for (int i = 1; i <= n; ++i) {
        res = min(res, dfs(i, -1));
    }
    
    cout << res << endl;
    
    return 0;
}

题目3:数字转换。

解题思路:先计算出数i的除去本身的约数之和j,然后j到i建立一条边。然后求树中的最长路径。注意,所有数最终一定会转到1上,所以1是根结点,且只有一颗树。

C++代码如下,

#include <iostream>
#include <unordered_map>
#include <vector>

using namespace std;

const int N = 50010;
int sum[N];
unordered_map<int, vector<int>> g;
bool st[N];
int res = 0;

int dfs(int a) {
    //返回以a为起点的最长路径。注意路径是指边。
    st[a] = true;
    
    int d1 = 0;//树中以a为起点的最长路径。注意路径是指边。
    int d2 = 0;//树中以a为起点的次长路径。
    
    for (int b : g[a]) {
        if (st[b]) continue; //走过的结点不能再走
        int d = dfs(b) + 1;
        if (d > d1) d2 = d1, d1 = d;
        else if (d > d2) d2 = d;
    }
    
    res = max(res, d1 + d2);
    return d1;
}

int main() {
    int n;
    cin >> n;
    
    for (int i = 1; i <= n; ++i) {
        //i是哪些j的约数,且j不等于i
        for (int j = i + i; j <= n; j += i) {
            sum[j] += i;
        }
    }
    
    for (int i = 1; i <= n; ++i) {
        int j = sum[i]; //j表示i除去本身的约数之和
        //建立一条边j->i 
        if (j == 0) continue; //特判0,这个特殊情况。因为题目中要求转换过程中,必须都是正整数。
        if (j >= i) continue; //建立这条边的条件,除去本身的约数之和小于本身
        
        g[j].emplace_back(i);
    }
    
    dfs(1); //转换过程中肯定会指向1嘛?一定!
    
    cout << res << endl;
    
    return 0;
}

题目4:二叉苹果树。

解题思路:状态计算那儿有些没理解

C++代码如下,

#include <iostream>
#include <unordered_map>
#include <vector>

using namespace std;

const int N = 110;
int n, m;
unordered_map<int, vector<pair<int,int>>> g; //first是结点,second是权重
int f[N][N];

void dfs(int a, int fa) {
    for (auto [b, c] : g[a]) {
        if (b == fa) continue;
        dfs(b, a);
        for (int j = m; j >= 0; --j) {
            for (int k = 0; k + 1 <= j; ++k) {
                f[a][j] = max(f[a][j], f[a][j - k - 1] + f[b][k] + c);
            }
        }
    }  
    return;
}

int main() {
    cin >> n >> m;
    for (int i = 1; i < n; ++i) {//n-1条边
        int a, b, c;
        cin >> a >> b >> c;
        g[a].emplace_back(b,c);
        g[b].emplace_back(a,c);
    }
    
    dfs(1, -1);
    
    cout << f[1][m] << endl;
    
    return 0;
}

题目5:战略游戏。

状态定义f[i][0]:表示在以结点i为根的子树中选择,且不选择结点i的case。该情况下的最小值。
状态定义f[i][1]:表示在以结点i为根的子树中选择,且选择结点i的case。请情况下的最小值。

状态转移,
f [ i ] [ 0 ] = m i n ( f [ i ] [ j ] ,    ∑ i f [ s i ] [ 1 ] ) f[i][0] = min(f[i][j],\ \ \sum_if[s_i][1]) f[i][0]=min(f[i][j],  if[si][1])
f [ i ] [ 1 ] = m i n ( f [ i ] [ j ] ,    ∑ i m i n ( f [ s i ] [ 0 ] ,    f [ s i ] [ 1 ] ) ) f[i][1] = min(f[i][j], \ \ \sum_imin(f[s_i][0], \ \ f[s_i][1])) f[i][1]=min(f[i][j],  imin(f[si][0],  f[si][1]))
其中结点i可以走到 s i s_i si

C++代码如下,

#include <iostream>
#include <unordered_map>
#include <vector>
#include <cstring>
#include <cstdio>

using namespace std;

const int N = 1510;
int n;
unordered_map<int, vector<int>> g;
bool st[N];
int f[N][2];

void dfs(int a) {
    f[a][0] = 0;
    f[a][1] = 1;

    for (auto b : g[a]) {
        dfs(b);

        f[a][0] += f[b][1];
        f[a][1] += min(f[b][0], f[b][1]);
    }
    return;
}

int main() {
    while (scanf("%d", &n) != -1) {
        //多组测试数据,初始化相应变量
        memset(st, 0, sizeof st);
        memset(f, 0, sizeof f);
        g.clear(); 

        for (int i = 0; i < n; ++i) {
            int a, cnt;
            scanf("%d:(%d)", &a, &cnt);
            for (int j = 0; j < cnt; ++j) {
                int b;
                scanf("%d", &b);

                //a->b存在一条有向边
                g[a].emplace_back(b);
                st[b] = true; //b不是根结点
            }
        }

        int root = -1;
        for (int i = 0; i < n; ++i) {
            if (!st[i]) {
                root = i;
                break;
            }
        }

        dfs(root);

        cout << min(f[root][0], f[root][1]) << endl;
    }

    return 0;
}

题目6:皇宫看守。

解题思路:暂时没有看懂

C++代码如下,

#include <iostream>
#include <unordered_map>
#include <vector>

using namespace std;

const int N = 1510;
int n;
int w[N]; //w[i]表示在结点i安排守卫的费用
bool st[N]; //st[i]=true表示i不是根结点
unordered_map<int, vector<int>> g;
int f[N][N];

void dfs(int a) {
    f[a][2] = w[a];
    
    int sum = 0;
    for (auto b : g[a]) {
        dfs(b);
        
        f[a][0] += min(f[b][1], f[b][2]);
        f[a][2] += min(min(f[b][0], f[b][1]), f[b][2]); 
        sum += min(f[b][1], f[b][2]);
    }
    
    f[a][1] = 1e9;
    for (auto b : g[a]) {
        f[a][1] = min(f[a][1], sum - min(f[b][1], f[b][2]) + f[b][2]);
    }
    
    return;
}

int main() {
    cin >> n;
    for (int i = 0; i < n; ++i) {
        int a, wi, cnt;
        cin >> a >> wi >> cnt;
        w[a] = wi;
        for (int j = 0; j < cnt; ++j) {
            int b;
            cin >> b;
            g[a].emplace_back(b);
            st[b] = true;
        }
    }
    
    int root = -1;
    for (int i = 1; i <= n; ++i) {
        if (!st[i]) {
            root = i;
            break;
        }
    }
    
    dfs(root);
    
    cout << min(f[root][1], f[root][2]) << endl;
    
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

YMWM_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值