DFS序处理子树问题的应用

DFS序处理子树问题的应用

概念

DFS序,也就是通常所说的 \(dfn\) 。记录的是树的每一个节点在深度优先搜索遍历中被访问的时间戳,例如:

graph TB a((1)) b((2)) c((3)) d((4)) e((5)) f((6)) g((7)) h((8)) i((9)) j((10)) k((11)) l((12)) a---b b---c b---f c---d c---e a---g g---h a---i i---j i---k i---l

上图中节点中的数字就是该节点DFS序,当然这不是唯一的答案(手动模拟试试!!!)。

显然根据DFS “不搜到底不回头”的性质:对于每一个节点来说,其兄弟节点的DFS序一定大于它的所有后代(访问时间一定晚!),、。

那么对于一个节点 \(u\) 来说,它以及它的子树内的节点的DFS序均大于 \(u\) 的前一个访问的兄弟,小于后一个访问的兄弟,形成了一段DFS序连续的区间。

例如上图:

  • \(2\) 为根的子树中将DFS序排序后可得:\({2,3,4,5,6}\)

  • \(9\) 为根的子树中将DFS序排序后可得:\({9,10,11,12}\)

总结出一个性质:一棵子树内,DFS序连续(学过树剖的表示很熟悉),这样就可以线性处理一颗子树了o( ̄▽ ̄)ブ

例题

树上背包(数据加强)

给定一颗 \(n\) 个节点的树,每一个节点有一个重量 \(W_u\) 和价值 \(V_u\)。选择若干个节点满足条件:

  1. \(\sum W_u\le m\)

  2. 如果要选择节点 \(u\) ,则 \(u\) 的父亲必选

问最大的 \(\sum V_u\)

数据范围:\(1\le n,m\le 2\times 10^3,0\le W_u\le 1\times10^4,0\le |V_u|\le 1\times10^4\)

分析

如果用普通的树上背包方式,时间复杂度为 \(O(nm^2)\),显然无法通过本题,考虑其他思路。

如果将条件2删除,我们就会发现这道题变成了一个线性背包,状态转移方程如下:

\[f_{i,j}=\begin{cases} \max\{f_{i-1,j},f_{i-1,j-W_i}+V_i\}\quad(j\ge W_i) \\\\ f_{i-1,j}\quad(j< W_i) \end{cases} \]

如果只能选择 \(i\),那么这个方程同样适用于原题。

那么问题就是如何处理不选的情况,我们发现如果节点 \(u\) 不选,则整颗子树均不选这似乎是废话),那可以想到一个思路:只要 \(f_{i,j}\) 是从一个 \(i\) 的子树均不选择(没被考虑)的决策位置转移过来,那就没有问题了。

但子树中各个节点位置不确定,难以统计,这时就需要用到上面提到的DFS序了!

对每一个节点按照其DFS序排序后,一棵子树内的节点被排列再来一个区间:

\[[i,i+sz_i-1]\quad\ (sz_i为以i为根的子树的大小) \]

由于根节点在前面,我们倒序进行DP。这时,不选的情况 \(f_{i,j}=f_{i+1,j+sz_i}\),可以从不是子树的节点转移!

\[f_{i,j}=\begin{cases} \max\{f_{i+1,j+sz_i},f_{i+1,j-W_i}+V_i\}\quad(j\ge W_i) \\\\ f_{i+1,j+sz_i}\quad(j< W_i) \end{cases} \]

代码

#include <bits/stdc++.h>
using namespace std;
const int maxn = 2e3,INF = 1e9;
struct node {
  int v,w; 
  int sz,pos;
};
int n,m;
vector<int> G[maxn + 5];
int dfn[maxn + 5],dfncnt = 0;
node a[maxn + 5];
int dp[maxn + 5][maxn + 5]; // 后i个物品,背包体积为j的答案(一定选u)
void init(int u,int fa) {
  dfn[u] = ++ dfncnt,a[u].sz = 1;
  for (auto v : G[u]) {
    if (v == fa) continue;
    init(v,u);
    a[u].sz += a[v].sz;
  }
}
bool comp(const node& x,const node& y) { return x.pos < y.pos; }
int main() {
  scanf("%d %d",&n,&m);
  for (int i = 0;i <= n;i ++) for (int j = 0;j <= m;j ++) dp[i][j] = -INF;
  for (int i = 1;i <= n;i ++) scanf("%d %d",&a[i].v,&a[i].w);
  for (int i = 1;i <= n - 1;i ++) {
    int u,v; scanf("%d %d",&u,&v);
    G[u].push_back(v);
    G[v].push_back(u);
  }
  init(1,0);
  for (int i = 1;i <= n;i ++) a[i].pos = dfn[i];
  sort(a + 1,a + n + 1,comp);
  for (int i = 0;i <= m;i ++) dp[n + 1][i] = 0;
  for (int i = n;i >= 1;i --) {
    for (int j = 0;j <= m;j ++) {
      dp[i][j] = dp[i + a[i].sz][j]; // 不选u->不选u这颗子树
      if (j >= a[i].w) dp[i][j] = max(dp[i][j],dp[i + 1][j - a[i].w] + a[i].v); // 选u
    }
  }
  printf("%d",dp[1][m]);
  return 0;
}
posted @ 2025-09-09 23:36  nightmare_lhh  阅读(5)  评论(0)    收藏  举报