一.洛谷P3177
题目大意:
给你一颗含n个点的树,让你选出k个黑点,其他都为白点。然后贡献为同颜色点之间两两路径和.最大化贡献。
k
≤
n
≤
2000
k \leq n \leq 2000
k≤n≤2000
题目思路:
1.首先定义状态
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j]为节点i,有j个黑点的最大值
这样以来,我们可以将一颗子树的黑点个数看作是背包的体积。然后二元组<子树中黑点个数,点对贡献>就代表着一个物品。对于每个子树来说,只能向根转移一种二元组(黑点个数0~min(子树大小,k)).
问题就转化为树上分组背包,体积恰好为k的最大价值.
2.计算点对贡献
由于这个题目中k是固定了的。所以对于一个子树x.若含有j个黑点,那么x的补集树就含有 k - j 个黑点,白点个数一样。所以贡献为:
( j ∗ ( k − j ) + ( s z [ x ] − j ) ∗ ( n − k − s z [ x ] + j ) ) ∗ w [ i ] (j * (k - j) + (sz[x] - j)*(n - k -sz[x]+j))*w[i] (j∗(k−j)+(sz[x]−j)∗(n−k−sz[x]+j))∗w[i]
注意:一定要控制循环上界为树的大小sz。这样才能保证每个点对只在它们的LCA处计算一次.复杂度为O(n^2).
AC代码(模板):
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define pii pair<int,int>
#define pb push_back
#define mp make_pair
const int maxn = 2000 + 5;
const int mod = 1e9 + 7;
ll dp[maxn][maxn];
ll sz[maxn] , n;
vector<pii> e[maxn];
ll tmp[maxn] , up;
void dfs (int u , int fa)
{
sz[u] = 1;
for (auto g : e[u]){
int v = g.first;
ll w = g.second;
if (v == fa) continue;
dfs(v , u);
for (int i = 0 ; i <= sz[u] + sz[v]; i++) tmp[i] = dp[u][i];
for (int i = 0 ; i <= min(sz[u] , up) ; i++){
for (int j = 0 ; j <= min(sz[v] , up) ; j++){
if (i + j > up) continue;
ll val = 1ll * j * (up - j) /*黑点*/ + 1ll * (sz[v] - j) * (n - sz[v] - (up - j));
val *= w;
tmp[i + j] = max (tmp[i + j] , dp[u][i] + dp[v][j] + val);
}
}
for (int i = 0 ; i <= sz[u] + sz[v] ; i++) dp[u][i] = tmp[i];
sz[u] += sz[v];
}
return ;
}
int main()
{
ios::sync_with_stdio(false);
cin >> n >> up;
for (int i = 1 ; i < n ; i++){
int x , y , z; cin >> x >> y >> z;
e[x].pb (mp(y , z));
e[y].pb (mp(x , z));
}
dfs (1 , 0);
cout << dp[1][up] << endl;
return 0;
}
二.洛谷P1272
题目大意:
给你一棵树,让你删除最少的边使得生成一个含有 P P P个节点的连通块.
题目思路:
令
f
(
i
,
j
)
f(i,j)
f(i,j)代表以
i
i
i节点为根,向上提供
j
j
j个节点的最少代价.
跑完树形背包后遍历每个节点即可。除了根节点,其他节点花费均需要
+
1
+1
+1
AC代码:
for (int i = 1 ; i <= sz[u] ; i++){
for (int j = 0 ; j <= sz[v] ; j++){
tmp[i + j] = min (tmp[i + j] , dp[u][i] + dp[v][j]);
}
}
三.洛谷P1273
题目大意:
给你一棵树,每个叶子节点有它的价值。选择一个叶子节点将产生的花费为 【从叶子节点到根节点的路径权值和】. 一条边只会被算一次花费。问你在不亏本的情况下最多选多少个叶子节点.
题目思路:
问题结构:叶子节点的选择之间有影响。树上依赖背包.
首先我想的是:
f
(
i
,
j
)
f(i,j)
f(i,j)代表以
i
i
i为根节点,整棵树获得价值
j
j
j的情况下最多选择多少个叶子节点.
但是价值的状态太大,考虑换意:令
f
(
i
,
j
)
f(i,j)
f(i,j)代表…,在选择了
j
j
j个叶子节点后所能得到的最大价值.
然后最后对根节点,求最大的
j
j
j使得
f
(
i
,
j
)
≥
0
f(i,j) \geq 0
f(i,j)≥0。即得到答案.
AC代码:
void dfs (int u , int fa)
{
sz[u] = 1;
dp[u][0] = 0;
for (auto g : e[u]){
int v = g.first;
int w = g.second;
if (v == fa) continue;
dfs(v , u);
for (int i = 1 ; i <= sz[u] + sz[v] ; i++)
tmp[i] = -inf;
for (int i = 0 ; i <= sz[u] ; i++){
for (int j = 0 ; j <= sz[v] ; j++){
tmp[i + j] = max (tmp[i + j] , dp[u][i] + dp[v][j] - (j == 0 ? 0 : w));
}
}
for (int i = 0 ; i <= sz[u] + sz[v] ; i++)
dp[u][i] = tmp[i];
sz[u] += sz[v];
}
if (sz[u] == 1){
dp[u][1] = a[u];
}
return ;
}
四.HDU6540
题目大意:
给你一棵树,然后有
m
m
m个关键点。有多少种选择方案使得他们两两之间的距离
≤
k
\leq k
≤k
n
,
m
,
k
≤
5000
n,m,k \leq 5000
n,m,k≤5000
题目思路:
容易想到
f
(
i
,
j
)
f(i,j)
f(i,j)代表以i为根,且离
i
i
i最远的关键点离它距离为
j
j
j的方案数。
然后在转移的时候考虑将子树接在主树上时要满足条件
a
+
b
+
1
≤
k
a+b+1 \leq k
a+b+1≤k才能转移.
注意,这样的转移忽略了主树没选一个关键点 或者 子树没选一个关键点 的两种情况。我们在预处理 t m p tmp tmp数组的时候额外转移一下即可。
AC代码:
void dfs (int u , int fa)
{
sz[u] = 1;
dp[u][0] = a[u];
for (auto v : e[u]){
if (v == fa) continue;
dfs(v , u);
// 预处理 子树不选一个关键点的方案数
for (int i = 0 ; i <= sz[u] + sz[v] ; i++)
tmp[i] = dp[u][i];
// 预处理 主树不选一个关键点的方案数
for (int i = 0 ; i <= sz[v] ; i++)
tmp[i + 1] = (tmp[i + 1] + dp[v][i])%mod;
// 子树和主树都选了关键点,那么形成路径,跑dp即可.
for (int i = 0 ; i <= sz[u] ; i++){
for (int j = 0 ; j <= sz[v] ; j++){
if (i + j + 1 > k) continue;
tmp[max(i , j + 1)] = (tmp[max(i , j + 1)] + 1ll * dp[u][i] * dp[v][j] %mod)%mod;
}
}
for (int i = 0 ; i <= sz[u] + sz[v] ; i++)
dp[u][i] = tmp[i];
sz[u] += sz[v];
}
return ;
}
五.牛客-杀树
题目大意:
给出一棵节点数为
n
n
n 的树,删去一个点
i
i
i 的代价为
a
i
a_i
ai
一条链的长度定义为路径上 点 的个数。一棵树死了,满足不存在一条长度
≥
l
\geq l
≥l 的链,牛牛希望用最少代价杀死这棵树。
n
,
l
,
a
i
≤
5000
n,l,a_i \leq 5000
n,l,ai≤5000
题目思路:
定义 f ( i , j ) f(i,j) f(i,j)为 r o o t = i root = i root=i,向上提供长度为j的链时,最小代价.
特判预处理:
1.当我们将
i
i
i节点删掉时,无论子树如何贡献长度,总体向上贡献长度还是0.
2.当
l
=
1
l=1
l=1时,边界条件
f
(
i
,
1
)
=
0
f(i,1)=0
f(i,1)=0非法!
AC代码:
void dfs (int u , int fa)
{
sz[u] = 1;
dp[u][0] = a[u];
if (k != 1)
dp[u][1] = 0;
for (auto v : e[u]){
if (v == fa) continue;
dfs(v , u);
for (int i = 0 ; i <= sz[u] + sz[v] ; i++)
tmp[i] = inf;
// 主树不向上提供链长
for (int i = 0 ; i <= sz[v] ; i++){
tmp[0] = min (tmp[0] , dp[u][0] + dp[v][i]);
}
for (int i = 1 ; i <= sz[u] ; i++){
for (int j = 0 ; j <= sz[v] ; j++){
if (i + j >= k) continue;
tmp[max(i,j+1)] = min (tmp[max(i,j+1)] , dp[u][i] + dp[v][j]);
}
}
for (int i = 0 ; i <= sz[u] + sz[v] ; i++)
dp[u][i] = tmp[i];
sz[u] += sz[v];
}
return ;
}
六.蓝桥杯-金属采集
题目大意:
给你一棵带边权的树,放
k
k
k个机器人在
s
s
s点。让你安排这
k
k
k个机器人遍历完整颗树,使得价值最小.
n
≤
1
e
5
,
k
≤
10
n \leq 1e5 , k \leq 10
n≤1e5,k≤10
题目思路:
令 f ( i , j ) f(i,j) f(i,j)代表 r t = i rt = i rt=i,最终有 j j j个机器人停在该子树中的最小花费.
f ( i , 0 ) f(i,0) f(i,0)代表最终无人停留在子树中,也就是派了一个机器人遍历整棵树.那么边 * 2.(一来一回的花费)
因为机器人可以随意移动。它可以先进入一颗子树,再出来。
为什么不会有这样的情况:分配两个机器人进入子树 i i i,然后再让一个机器人出来.因为这种状态等同于 f ( i , 1 ) f(i,1) f(i,1)
AC代码:
void dfs (int u , int fa)
{
sz[u] = 1;
for (int i = 0 ; i < (int)e[u].size() ; i++){
pii g = e[u][i];
int v = g.first;
int w = g.second;
if (v == fa) continue;
dfs(v , u);
for (int i = 0 ; i <= min(sz[u] + sz[v] , k) ; i++)
tmp[i] = inf;
for (int i = 0 ; i <= min(sz[u] , k) ; i++){
for (int j = 0 ; j <= min(sz[v] , k) ; j++){
if (i + j > k) continue;
tmp[i + j] = min (tmp[i + j] , dp[u][i] + dp[v][j] + (j == 0 ? 2 : j) * w);
}
}
for (int i = 0 ; i <= min(sz[u] + sz[v] , k) ; i++)
dp[u][i] = tmp[i];
sz[u] += sz[v];
}
return ;
}
七.ACWING-有依赖的背包问题
题目大意:
给你一棵含有
N
N
N个点的树,每个节点是一个物品。每个物品有体积和价值。一个物品选了,那么他的所有祖先节点都需要被选。问你整棵树在容量为
V
V
V能够获得的最大价值
N
,
V
≤
100
N,V \leq 100
N,V≤100
题目思路:
一样的套树上背包模板.令
f
(
i
,
j
)
f(i,j)
f(i,j)为i点,体积为
j
j
j的最大价值.
千万注意,对于每个节点,先将其看作是必选了该节点的物品的背包.(如何实现 “必须选某个物品下”的最优解?初始化的时候,体积为
[
0
,
w
[
i
]
−
1
]
[0,w[i]-1]
[0,w[i]−1]的都赋值为
−
i
n
f
-inf
−inf,
[
w
[
i
]
,
V
]
[w[i],V]
[w[i],V]的都赋值为
v
[
i
]
v[i]
v[i],然后转移即可. 或者,先背其他物品,最后的时候,将
w
[
i
]
w[i]
w[i]的体积让出来放该物品,即
∀
i
∈
[
w
[
i
]
,
V
]
,
d
′
[
i
]
=
d
[
i
−
w
[
i
]
]
+
v
[
i
]
\forall i\in [w[i],V],d'[i]=d[i-w[i]]+v[i]
∀i∈[w[i],V],d′[i]=d[i−w[i]]+v[i])
AC代码:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define pii pair<int,int>
#define pb push_back
#define mp make_pair
const int maxn = 100 + 5;
const int mod = 1e9 + 7;
int v[maxn] , w[maxn];
vector<int> e[maxn];
int dp[maxn][maxn] , n , V;
int tmp[maxn];
void dfs (int u , int fa)
{
// 本节点必选
for (int i = 0 ; i <= V ; i++){
if (i >= w[u])
dp[u][i] = v[u];
else
dp[u][i] = -1e9;
}
for (auto v : e[u]){
if (v == fa) continue;
dfs(v , u);
// 本节点必选
for (int i = 0 ; i <= V ; i++)
tmp[i] = -1e9;
for (int i = 0 ; i <= V ; i++)
for (int j = 0 ; i + j <= V ; j++)
tmp[i + j] = max (tmp[i + j] , dp[u][i] + dp[v][j]);
for (int i = 0 ; i <= V ; i++)
dp[u][i] = tmp[i];
}
dp[u][0] = 0;
}
int main()
{
ios::sync_with_stdio(false);
cin >> n >> V;
int rt;
for (int i = 1 ; i <= n ; i++){
cin >> w[i] >> v[i];
int x; cin >> x;
if (x == -1) rt = i;
else e[x].pb (i);
}
dfs (rt , 0);
cout << dp[rt][V] << endl;
return 0;
}
八.ICPC2020南京M
题目大意:
题目思路:
AC代码:
九.HDU6769:二分答案+树上背包(较难)
https://www.jianshu.com/writer#/notebooks/44190750/notes/76658492