洛谷P3354 河流

有点权边权的树,选出k个关键点,根必须选。每个点的贡献为点权 * 到最近的关键祖先的距离。求最小总贡献。

解:树形DP是最毒瘤的算法......

设fxij表示以x为根的子树中选了j个关键点,且x的最近关键祖先是它的i级祖先时的最小贡献。

初态:fxi0 = val[x] * dis(),fx01 = 0。

转移:注意到各个i之间是分层转移的,只有女儿i = 0的情况会转移到母亲的每个i。

于是先转移fy0r,再分层转移fyjr。每层是一个树上背包。

 1 #include <bits/stdc++.h>
 2 
 3 typedef long long LL;
 4 const int N = 110;
 5 
 6 struct Edge {
 7     int nex, v, len;
 8 }edge[N]; int tp;
 9 
10 int e[N], n, siz[N], fa[N], val[N], k;
11 LL f[N][N][N], temp[N][N], d[N];
12 
13 inline void add(int x, int y, int z) {
14     tp++;
15     edge[tp].v = y;
16     edge[tp].len = z;
17     edge[tp].nex = e[x];
18     e[x] = tp;
19     return;
20 }
21 
22 inline LL dis(int x, int t) {
23     int y = x;
24     for(int i = 1; i <= t; i++) {
25         y = fa[y];
26     }
27     return d[x] - d[y];
28 }
29 /*
30 2 1
31 1 0 2
32 1 0 3
33 ------------- 2
34 */
35 void DFS(int x) {
36     //printf("x = %d d[x] = %lld  fa[x] = %d \n", x, d[x], fa[x]);
37     siz[x] = 1;
38     for(int i = 1; i <= n; i++) {
39         f[x][i][0] = val[x] * dis(x, i);
40         //printf("f %d %d %d  = %lld = %d * %d\n", x, i, 0, f[x][i][0], val[x], dis(x, i));
41     }
42     f[x][0][1] = 0;
43     for(int i = e[x]; i; i = edge[i].nex) {
44         int y = edge[i].v;
45         //printf("%d -> %d \n", x, y);
46         d[y] = d[x] + edge[i].len;
47         DFS(y);
48         memcpy(temp, f[x], sizeof(f[x]));
49         memset(f[x], 0x3f, sizeof(f[x]));
50         for(int j = 0; j <= n; j++) { /// dis
51             for(int p = 1; p <= k; p++) {
52                 for(int r = 1; r <= p; r++) {
53                     //printf("r = %d -> %lld \n", r, temp[j][p - r] + f[y][0][r]);
54                     f[x][j][p] = std::min(f[x][j][p], temp[j][p - r] + f[y][0][r]); /// don't choose
55                 }
56                 //printf("1f %d %d %d =  %lld \n", x, j, p, f[x][j][p]);
57             }
58         }
59         for(int j = 0; j <= n; j++) {
60             for(int p = k; p >= 0; p--) {
61                 for(int r = 0; r <= p; r++) {
62                     f[x][j][p] = std::min(f[x][j][p], temp[j][p - r] + f[y][j + 1][r]); /// choose y
63                 }
64                 //printf("2f %d %d %d =  %lld \n", x, j, p, f[x][j][p]);
65             }
66         }
67         siz[x] += siz[y];
68     }
69     //printf("%d return \n", x);
70     return;
71 }
72 
73 int main() {
74     memset(f, 0x3f, sizeof(f));
75     scanf("%d%d", &n, &k);
76     n++; k++;
77     k = std::min(k, n);
78     for(int i = 2, x, y; i <= n; i++) {
79         scanf("%d%d%d", &val[i], &x, &y);
80         add(x + 1, i, y);
81         fa[i] = x + 1;
82     }
83 
84     DFS(1);
85 
86     printf("%lld\n", f[1][0][k]);
87     return 0;
88 }
AC代码

注意每次合并子树的时候,原来的值不能保留。保留下来的要加上fy0r

恶心死我了...

猜你喜欢

转载自www.cnblogs.com/huyufeifei/p/10517880.html