[IOI2005]Riv 河流
一道不简单的DP题。我一开始设\(f[u][j]\)表示以u为根的子树中,建立\(j\)个伐木场的最小运费,并且假定u为入海口。转移的时候,因为需要记录子树中有多少是直接流向u的,所以开一个数组
\(g[u][j]\)表示以u为根的子树中有多少是直接流向u的。然而在转移的时候出现了问题。当从v转移到u时,我直接将流向v的货物又按照直接流向u进行计算的,这样显然是错误的。因为在v中最优的选择不一定是在u中也是最优的。我们还需要记录一个状态anc,表示所有的节点都流向anc,然后就可以大力转移了。
#include <iostream>
#include <cstring>
#include <cstdio>
using namespace std;
const int N = 100 + 10;
typedef long long LL;
struct Edge {
int v, d, nxt;
} e[N << 1];
int head[N], cnt;
int wgt[N], g[N][N], n, m, INF = 2e9 + 10;
int stk[N], idx, dis[N];
LL f[N][N][N][2], tmp[N][N];
void AddEdge(int u, int v, int d) {
e[ ++ cnt].v = v;
e[cnt].d = d;
e[cnt].nxt = head[u];
head[u] = cnt;
}
void dfs(int u) {
stk[++ idx] = u;
for(int i = head[u]; i; i = e[i].nxt) {
int v = e[i].v;
dis[v] = dis[u] + e[i].d;
// printf("dep[%d] = %d\n", v, dis[v]);
dfs(v);
for(int k = 1; k <= idx; k ++) {//都流向节点anc
for(int j = m; j >= 0; -- j) {//j个伐木场
int anc = stk[k];
f[u][j][anc][0] += f[v][0][anc][0];
f[u][j][anc][1] += f[v][0][u][0];
for(int l = 0; l <= j; l ++) {
f[u][j][anc][0] = min(f[u][j][anc][0], f[u][j - l][anc][0] + f[v][l][anc][0]);
f[u][j][anc][1] = min(f[u][j][anc][1], f[u][j - l][anc][1] + f[v][l][u][0]);
}
}
}
}
for(int k = 1; k <= idx; k ++) {//都流向节点anc
for(int j = 0; j <= m; ++ j) {//j个伐木场
int anc = stk[k];
if( j >= 1)
f[u][j][anc][0] = min(f[u][j][anc][0] + wgt[u] * (dis[u] - dis[anc]), f[u][j - 1][anc][1]);
else f[u][j][anc][0] += wgt[u] * (dis[u] - dis[anc]);
}
}
idx --;
}
int main()
{
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i ++) {
int fa, d;
scanf("%d%d%d", &wgt[i], &fa, &d);
AddEdge(fa, i, d);
}
dfs(0);
printf("%lld\n", f[0][m][0][0]);
}