树形 dp

树上dp

  • 例题
    • 1
      • 3
  • 换根dp
    • 例题
    • 1
      • 解题思路
  • 题目描述
      • 输入格式
      • 输出格式
  • 代码实现


例题

1

在这里插入图片描述

long long a[N],dp[N][2];//a[]用来存储快乐指数
//dp[u][0]表示在以u为根节点的子树中选择两两不相邻的若干个点,不选点u的最大权值和
//dp[u][1]表示在以u为根节点的子树中选择两两不相邻若干个点,选了点u的最大权值和 

vector<int> e[N];//用vector来存储树的边
set<int> st;//用set来找根节点

void dfs(int v)
{
  for(auto u:e[v])//遍历v的所有子节点u
  {
    dfs(u);//从u开始遍历搜索整个树
    dp[v][1]=dp[v][1]+dp[u][0];//选了点v表明此时不能选子节点
    dp[v][0]=dp[v][0]+max(dp[u][0],dp[u][1]);//不选点v表明此时可以选其中一个子节点或不选的情况下得到的最大值
  }
  dp[v][1]+=a[v];//初始化根节点的权值
}

int main()
{
  int n;
  cin>>n;
  for(int i=1;i<=n;i++)
  {
    cin>>a[i];//输入每个人的欢乐指数
    st.insert(i);//每个人一开始都可能是根节点,将每个人都存入到set中去
  }
  for(int i=1;i<n;i++)
  {
    int u,v;
    cin>>u>>v;
    e[v].push_back(u);//v是u的直接上司,所以在e[v]中添加u表明u是v的子节点
    st.erase(u);//u上面有v说明u不是董事长,所以把u从set中删除(set中存储可能是根节点的人)
  }
  int root=*st.begin();//经过确定直接上司后对set删除的操作最终找到根节点(即董事长)
  dfs(root);//从根节点开始搜索
  cout<<max(dp[root][0],dp[root][1])<<endl;//找出从根节点开始分别选根节点与不选根节点的搜索情况下的最大值
  return 0;
}

3

在这里插入图片描述
在这里插入图片描述

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

const int N = 100010;
vector<pair<int, int>> g[N];
int d1[N], d2[N], up[N], p1[N], p2[N];
int n, m, k;

void dfs1(int u, int f) {
    for (auto v : g[u]) {
        if (v.first == f) continue;
        dfs1(v.first, u);
        if (d1[v.first] + v.second >= d1[u]) {
            d2[u] = d1[u];
            p2[u] = p1[u];
            d1[u] = d1[v.first] + v.second;
            p1[u] = v.first;
        } else if (d1[v.first] + v.second > d2[u]) {
            d2[u] = d1[v.first] + v.second;
            p2[u] = v.first;
        }
    }
}

void dfs2(int u, int f) {
    for (auto v : g[u]) {
        if (v.first == f) continue;
        if (p1[u] == v.first)
            up[v.first] = v.second + max(up[u], d2[u]);
        else
            up[v.first] = v.second + max(up[u], d1[u]);
        dfs2(v.first, u);
    }
}

int main() {
    int t = 1;
    for (int zu = 1; zu <= t; zu++) {
        cin >> n;
        for (int i = 1; i <= n - 1; i++) {
            int a, b, c;
            cin >> a >> b >> c;
            g[a].push_back(make_pair(b, c));
            g[b].push_back(make_pair(a, c));
        }
        dfs1(1, -1);
        dfs2(1, -1);
        long long res = 0;
        for (int i = 1; i <= n; i++) {
            res = max(res, max(1LL * d1[i] * d2[i], 1LL * d1[i] * up[i]));
        }
        cout << res << endl;
    }
    return 0;
}

换根dp

例题

1

在这里插入图片描述

解题思路

首先,我们需要计算每个节点的深度,对于一个节点 v,它的深度为 depth[v]。我们可以通过一次深度优先搜索计算出所有节点的深度。

然后我们引入几个辅助变量。对于节点 v,定义 down1[v]down2[v] 分别为节点 v 的子树中到叶子节点距离的前两大值。同时,定义 up[v] 为节点 v 子树之外的叶子节点到 v 的距离的最大值。

我们可以通过从底向上遍历树,维护两个最大的到叶子节点的距离,来重新计算 down1[v]down2[v] 的值。

假设节点 v 的祖先是 p,为了重新计算 up[v] 的值,需要向上遍历到节点 p,并找到离节点 p 最远的子树之外的叶子节点到 v 的距离。如果离节点 p 最远的叶子节点在节点 v 的子树中,那么需要取 down2[p] 的值,否则需要取 down1[p] 的值。

对于每个节点 v,定义其与根节点的最大距离为 dist[v] = max(down1[v], up[v])。

现在,对于每个节点 v,计算当以节点 v 为根节点时的树的代价。由于我们不希望进行额外的操作,因此操作的代价为 cost[v]。因此,树的代价将为 dist[v] - cost[v]

上述是换根 dp 的做法,但是这里有个最简单的做法,基于以下结论:找到树的一条直径的两个端点为 p 或者 q,那么对于树上的任意点,距离其最远的点一定是或者 p 或者 q,这是我们在学习用两次 DFS 求树的直径的贪心结论。根据这个结论 dist[v] 也可以定义为每个节点 vp 或者 q 的距离。这个距离可以直接 DFS 求出。

最后,遍历所有节点,取树的代价的最大值作为答案。很容易看出,这个解决方案的时间复杂度为 O(n)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 2e5 + 50;
vector<int> G[maxn];
ll d[maxn], d2[maxn], k, c;
void dfs(int v, int fa){
    for(int i = 0;i < G[v].size();i++){
        int u = G[v][i];
        if(u != fa){
            d[u] = d[v] + 1;
            dfs(u, v);
        }
    }
}
int main()
{
    int t;
    cin >> t;
    while(t--){
        int n;
        cin >> n;
        for(int i = 1;i <= n;i++) G[i].clear(), d[i] = d2[i] = 0;
        cin >> k >> c;
        for(int i = 0;i < n - 1;i++){
            int u, v;
            cin >> u >> v;
            G[v].push_back(u);
            G[u].push_back(v);
        }
        dfs(1, -1);
        int pos = 1;
        ll ans = 0;
        for(int i = 1;i <= n;i++){
             d2[i] = d[i];
             if(d[pos] < d[i]) pos = i;
             ans = max(ans, d[i] * k);
        }
        for(int i = 1;i <= n;i++) d[i] = 0;
        dfs(pos, -1);
        for(int i = 1;i <= n;i++){
            ans = max(ans, d[i] * k - c * d2[i]);
        }
        cout << ans << "\n";

    }
    return 0;
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
链接

#include <bits/stdc++.h>
using namespace std;
const int N = 5000 + 50;

vector<int> g[N];
int a[N], b[N], sz[N];
int dp[N][N];
int tmp[N];
int n, w;

void dfs(int u) {
    sz[u] = 1;
    dp[u][0] = 0;
    for (auto v: g[u]) {
        dfs(v);
        for (int i = 1; i <= sz[u] + sz[v]; i++) tmp[i] = 1e9;
        for (int j = 0; j <= sz[u]; j++) {
            for (int k = 0; k <= sz[v]; k++) {
                tmp[j + k] = min(tmp[j + k], dp[u][j] + dp[v][k]);
            }
        }
        sz[u] += sz[v];
        for (int j = 1; j <= sz[u]; j++) dp[u][j] = tmp[j];
    }
    dp[u][sz[u] - 1] = min(dp[u][sz[u] - 1], a[u]);
    dp[u][sz[u]] = min(dp[u][sz[u]], b[u]);
}

int main() {
    cin >> n >> w;
    for (int i = 1; i <= n; i++) cin >> a[i];
    memset(dp, 0x3f, sizeof(dp));
    b[1] = 1e9;
    for (int i = 2; i <= n; i++) {
        int fa, cost;
        cin >> fa >> cost;
        b[i] = cost;
        g[fa].push_back(i);
    }
    dfs(1);
    for (int i = n; i >= 0; i--) {
        if (dp[1][i] <= w) {
            cout << i << '\n';
            return 0;
        }
    }
    return 0;
}

题目描述

给定一棵树,树根为 1,每个点的点权为 V_i。你需要找出若干个点 P_i,使得:

  1. 每两个点 P_xP_y 互不相邻;
  2. 每两个点 P_xP_y 与树根的距离互不相同;
  3. 找出的点的点权之和尽可能大。

请输出找到的这些点的点权和的最大值。

输入格式

输入的第一行包含一个整数 n

第二行包含 n−1 个整数 F_i,相邻整数之间使用一个空格分隔,分别表示第 2n 个结点的父结点编号。

第三行包含 n 个整数 V_i,相邻整数之间使用一个空格分隔,分别表示每个结点的点权。

输出格式

输出一行包含一个整数表示答案。

代码实现

#include<iostream>
#include<cstring>
#include<vector>
#include<set>
using namespace std;
using ll = long long;
const int N = 3e5 + 7;

ll n, a[N], dp1[N], dp2[N], dp3[N], dep[N], fa[N], mxdep, ans = 0, lstmx;
vector<ll> ns[N], e[N]; // ns按深度存储节点,e存储树的邻接表
multiset<ll> st;

// 深度优先搜索,计算每个节点的深度,并按深度分类存储节点
void dfs(int x) {
    dep[x] = dep[fa[x]] + 1; // 当前节点深度 = 父节点深度 + 1
    mxdep = max(mxdep, dep[x]); // 更新最大深度
    ns[dep[x]].push_back(x); // 将当前节点存入对应深度的列表中
    for (auto i : e[x]) dfs(i); // 递归处理子节点
}

int main() {
    cin >> n; // 输入节点数量
    
    // 输入每个节点的父节点,构建树结构
    for (int i = 2; i <= n; i++) {
        cin >> fa[i];
        e[fa[i]].push_back(i); // 将i添加为fa[i]的子节点
    }
    
    dfs(1); // 从根节点1开始深度优先搜索
    
    // 输入每个节点的权值
    for (int i = 1; i <= n; i++) cin >> a[i];
    
    // 初始化根节点的动态规划值
    dp1[1] = a[1]; // dp1[i]表示选择节点i时的最大权值和
    dp2[1] = 0;    // dp2[i]表示不选择深度为i的任何节点时的最大权值和
    dp3[1] = 0;    // dp3[i]表示不选择节点i但选择同深度其他节点时的最大权值和
    lstmx = a[1];  // lstmx记录到当前深度为止的最大dp1值
    
    // 动态规划处理每个深度
    for (int i = 2; i <= mxdep; i++) {
        int mx = 0, secmx = 0, f; // mx和secmx记录当前深度节点的dp1最大值和次大值
        dp2[i] = lstmx; // 不选当前深度的节点,最大值为之前深度的最大值
        
        // 处理当前深度的每个节点
        for (auto j : ns[i]) {
            f = fa[j]; // 父节点
            // 选择j节点:不能选父节点,所以取dp2[i-1](不选i-1深度)或dp3[f](不选父节点但选同深度其他节点)
            dp1[j] = max(dp2[i - 1], dp3[f]) + a[j];
            lstmx = max(lstmx, dp1[j]); // 更新全局最大值
            
            // 更新当前深度的最大值和次大值
            if (dp1[j] > mx) {
                secmx = mx;
                mx = dp1[j];
            } else if (dp1[j] > secmx) {
                secmx = dp1[j];
            }
        }
        
        // 计算dp3:对于每个节点j,如果不选j,那么可以选择同深度的其他节点(最大值或次大值)
        for (auto j : ns[i]) {
            if (mx > dp1[j]) dp3[j] = mx; // 如果j不是最大值,则可以选最大值
            else dp3[j] = secmx;           // 否则选次大值
        }
    }
    
    // 最终答案是所有dp1和dp2中的最大值
    for (int i = 1; i <= n; i++) ans = max(ans, max(dp1[i], dp2[i]));
    cout << ans;
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值