DFS序处理子树问题的应用
DFS序处理子树问题的应用
概念
DFS序,也就是通常所说的 \(dfn\) 。记录的是树的每一个节点在深度优先搜索遍历中被访问的时间戳,例如:
上图中节点中的数字就是该节点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\)。选择若干个节点满足条件:
\(\sum W_u\le m\),
如果要选择节点 \(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删除,我们就会发现这道题变成了一个线性背包,状态转移方程如下:
如果只能选择 \(i\),那么这个方程同样适用于原题。
那么问题就是如何处理不选的情况,我们发现如果节点 \(u\) 不选,则整颗子树均不选(这似乎是废话),那可以想到一个思路:只要 \(f_{i,j}\) 是从一个 \(i\) 的子树均不选择(没被考虑)的决策位置转移过来,那就没有问题了。
但子树中各个节点位置不确定,难以统计,这时就需要用到上面提到的DFS序了!
对每一个节点按照其DFS序排序后,一棵子树内的节点被排列再来一个区间:
由于根节点在前面,我们倒序进行DP。这时,不选的情况 \(f_{i,j}=f_{i+1,j+sz_i}\),可以从不是子树的节点转移!
代码
#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;
}