例题一: 树上求和 link
思路:
考虑树形DP.
设f[x][0/1]表示第x个节点的子树的最大权值和,0,1表示选或不选这个节点,设y表示x的子节点。
f
[
x
]
[
0
]
=
m
a
x
(
f
[
y
]
[
0
]
,
f
[
y
]
[
1
]
)
f[x][0] = max(f[y][0], f[y][1])
f[x][0]=max(f[y][0],f[y][1])
f
[
x
]
[
1
]
=
f
[
y
]
[
0
]
+
r
x
f[x][1] = f[y][0] + r_x
f[x][1]=f[y][0]+rx
#include <cstdio>
#include <iostream>
using namespace std;
const int N = 6e3 + 10;
struct node {int to, next;} e[N << 1];
int n, r[N], x, y, l, k, head[N], cnt, fa[N], f[N][3];
void add(int x, int y) {e[++cnt] = (node){y, head[x]}, head[x] = cnt;}
void dfs(int x, int fa)
{
f[x][0] = 0, f[x][1] = r[x];
for(int i = head[x]; i; i = e[i].next)
{
int v = e[i].to;
if(v != fa)
{
dfs(v, x);
f[x][0] += max(0, max(f[v][0], f[v][1]));
f[x][1] += max(0, f[v][0]);
}
}
}
int main()
{
scanf("%d", &n);
for(int i = 1; i <= n; i++) scanf("%d", &r[i]);
for(int i = 1; i < n; i++) scanf("%d%d", &l, &k), add(k, l), fa[l] = k;
for(int i = 1; i <= n; i++)
{
if(!fa[i])
{
dfs(i, 0);
printf("%d\n", max(f[i][0], f[i][1]));
return 0;
}
}
}
例题二: 结点覆盖 link
思路:
一个点能覆盖自己父亲儿子,
那一个点一定会被它自己或者父亲或者儿子覆盖到。
设
f
i
,
0
/
1
/
2
f_{i,0/1/2}
fi,0/1/2为搞定i 的子树,
i 是被父亲 / 自己 / 儿子覆盖到的。
然后如果是自己覆盖记得要加上自己的费用,
后面就随意。
然后父亲就是儿子除了选父亲都可以随便。
然后儿子的话就是儿子也是不能选父亲,
而且要至少有一个选自己。
那你就看选儿子变成选自己损失最小的那个,让那个选。
然后这样转移就好啦,
最后输出的就是根节点被自己或儿子占领的最小费用。(根节点没有父亲)
#include <cstdio>
#include <iostream>
#define INF 0x3f3f3f3f
using namespace std;
const int N = 1e3 + 5e2 + 10;
struct node {int to, nxt;} e[N << 1];
int n, a[N], head[N], x, y, z, fa[N], cnt, rt, f[1501][3];
void add(int x, int y) {e[++cnt] = (node){y, head[x]}; head[x] = cnt;}
void dfs(int x, int fa)
{
f[x][1] = a[x];
int minn = INF;
for (int i = head[x]; i; i = e[i].nxt)
{
int v = e[i].to;
if (v != fa)
{
dfs(v, x);
f[x][0] += min(f[v][1], f[v][2]);
f[x][1] += min(f[v][0], min(f[v][1], f[v][2]));
f[x][2] += min(f[v][1], f[v][2]);
minn = min(minn, f[v][1] - min(f[v][1], f[v][2]));
}
}
f[x][2] += minn;
}
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; i++)
{
scanf("%d", &x);
scanf("%d", &a[x]);
scanf("%d", &y);
while (y--)
{
scanf("%d", &z);
fa[z] = x; add(x, z);
}
}
for (int i = 1; i <= n; i++)
if (!fa[i])
{
dfs(i, 0);
printf("%d\n", min(f[i][1], f[i][2]));
return 0;
}
}
例题三: 最长距离 link
思路:
考虑当前点到与他最远的节点的路径只有两种:
- 一种是往下走
- 另一种是先往上走再往下走。
我们设f[i][0/1/2]表示与当前点的最长路径。
0/1/2分别表示向下的最大距离,向下的次大距离,向上的最大距离。
首先考虑向下走的转移,设y表示当前点的子节点:
f
[
i
]
[
0
]
=
m
a
x
(
f
[
y
]
[
0
]
+
w
e
i
g
h
t
(
i
,
y
)
)
f[i][0] = max(f[y][0] + weight(i, y))
f[i][0]=max(f[y][0]+weight(i,y))
向下走求次大距离也是这样,注意优先级,
先更新最大值再更新次大值,更新最大值是也要更新次大值。
对于结点x,记与他距离最大的向下结点为index(x),显然:dis[x,index(x)] = weight(x, y);
f
[
y
]
[
2
]
=
m
a
x
(
f
[
x
]
[
1
]
,
f
[
x
]
[
2
]
)
,
y
≠
i
n
d
e
x
(
x
)
f[y][2] = max(f[x][1], f[x][2]),y \neq index(x)
f[y][2]=max(f[x][1],f[x][2]),y=index(x)
f
[
y
]
[
2
]
=
m
a
x
(
f
[
x
]
[
0
]
,
f
[
x
]
[
2
]
)
,
y
=
i
n
d
e
x
(
x
)
f[y][2] = max(f[x][0], f[x][2]),y = index(x)
f[y][2]=max(f[x][0],f[x][2]),y=index(x)
对于一个结点i,最后答案为max(f[i][0],f[i][2])
#include <cstdio>
#include <iostream>
#include <cstring>
using namespace std;
typedef long long ll;
const int N = 1e4 + 10;
struct node {ll to, next, val;} e[N << 1];
ll n, x, y, head[N], cnt, f[N][5], pl[N];
void csh()
{
memset(f, 0, sizeof(f));
memset(head, 0, sizeof(head));
cnt = 0;
}
void add(int x, int y, int z) {e[++cnt] = (node){y, head[x], z}, head[x] = cnt;}
void dfs1(ll x, ll fa)
{
ll maxn = 0, maxx = 0;
for(ll i = head[x]; i; i = e[i].next)
{
ll v = e[i].to;
if(v == fa) continue;
dfs1(v, x);
if(maxn <= f[v][0] + e[i].val) maxx = maxn, maxn = f[v][0] + e[i].val, pl[x] = v; //这里一定要有一个等于
else if(maxx < f[v][0] + e[i].val) maxx = f[v][0] + e[i].val;
else if(maxx < f[v][1] + e[i].val) maxx = f[v][1] + e[i].val;
}
f[x][0] = maxn;
f[x][1] = maxx;
}
void dfs2(ll x, ll fa)
{
for(ll i = head[x]; i; i = e[i].next)
{
ll v = e[i].to;
if(v != fa)
{
if(v == pl[x]) f[v][2] = max(f[x][1] + e[i].val, f[x][2] + e[i].val);
else f[v][2] = max(f[x][0] + e[i].val, f[x][2] + e[i].val);
dfs2(v, x);
}
}
}
int main()
{
while(scanf("%lld", &n) != EOF)
{
csh();
for(ll i = 2; i <= n; i++)
{
scanf("%lld%lld", &x, &y);
add(x, i, y);
}
dfs1(1, 0), dfs2(1, 0);
for(ll i = 1; i <= n; i++) printf("%lld\n", max(f[i][0], f[i][2]));
}
return 0;
}
例题四: 选课方案 link
思路:
考虑树形 DP。
然后发现范围可以
O
(
n
3
)
O(n^3)
O(n3) ,
可以直接放一个背包上去。
设
f
i
,
j
f_{i,j}
fi,j为搞定 i 的子树,i 要选,选了 j 个的最大点权和。
然后就从下向上转移,大概就是枚举总共的 j,再枚举这个子树用了多少次。
然后我们发现它是森林,你要合并答案。
然后就会自然的想到用 0 号点把森林变成树,然后要的次数就是 m+1 次,
0 号点的点权是 0,然后即可以了。
#include <cstdio>
#include <iostream>
using namespace std;
const int N = 3e2 + 10;
struct node {int to, next;} e[N << 1];
int n, m, k, s[N], f[N][N], head[N], cnt;
void add(int x, int y) {e[++cnt] = (node){y, head[x]}, head[x] = cnt;}
void dfs(int x, int fa)
{
for(int i = head[x]; i; i = e[i].next)
{
int v = e[i].to;
if(v == fa) continue;
dfs(v, x);
for(int t = m; t > 0; t--)
for(int j = t; j >= 0; j--)
f[x][t] = max(f[x][t], f[x][t - j] + f[v][j]);
}
if(x)
for(int t = m; t > 0; t--)
f[x][t] = f[x][t - 1] + s[x];
}
int main()
{
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i++) scanf("%d%d", &k, &s[i]), add(k, i);
dfs(0, 0);
printf("%d\n", f[0][m]);
return 0;
}