[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
", 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
", f[0][m][0][0]);
}
原文地址:https://www.cnblogs.com/wyy0804/p/13715180.html