P3354

题面

题意

这道题的大致意思就是,在一个以0号节点为根的树上,我需要在k个结点修建伐木场。然后对于每一个点,它对答案的贡献是他木头产量乘上它离下游最近伐木场的距离。让你合理分配伐木场安置距离以使得贡献最小。

分析

其实很容易就会想到用\(f_{i,j}\)来表示在i号点为根的子树上修建j个伐木场所得的最小贡献,但是我们发现状态并不好维护,所以我们需要改变一定的想法。

我们这里采用\(f_{i,j,k}\)表示在i为根的子树上修建k个伐木场,并且i下游的第一个伐木场在j,然后该状态下贡献的最小值。然后我们再记录\(g_{i,j,k}\)为i号点上建造伐木场,并且在i的子树上一共有k个伐木场,i下游的第一个伐木场是j,然后该状态下的最小值。

所以我们只要最后推到\(f_{0,0,k}\)然后输出就是最后答案了。

再考虑动态转移方程,很容易想到转移的过程就是一个背包的过程,然后为了记录某一结点所有的祖先,所以我们可以在dfs里加上手写栈(我比较懒,用了一个deque),这样只需要遍历栈就可以知道该点所有的祖先。

代码

简单背包模拟即可。

#include <bits/stdc++.h>
using namespace std;

const int maxn = 105;

struct edge
{
	int to, dist;
};

vector<vector<edge> > graph;

int c[maxn], depth[maxn];
long long f[maxn][maxn][maxn], g[maxn][maxn][maxn];
deque<int> st;
int n, k;

void dfs(int p)
{
	st.push_back(p);
	for (vector<edge>::iterator i = graph[p].begin(); i != graph[p].end(); i++)
	{
		depth[i->to] += depth[p] + i->dist;
		dfs(i->to);
		for (deque<int>::iterator j = st.begin(); j != st.end(); j++)
			for (int l = k; ~l; l--)
			{
				f[p][*j][l] += f[i->to][*j][0];
				g[p][*j][l] += f[i->to][p][0];
				for (int x = 0; x <= l; x++)
				{
					f[p][*j][l] = min(f[p][*j][l], f[p][*j][l - x] + f[i->to][*j][x]);
					g[p][*j][l] = min(g[p][*j][l], g[p][*j][l - x] + f[i->to][p][x]);
				}
			}
	}
	for (deque<int>::iterator j = st.begin(); j != st.end(); j++)
	{
		f[p][*j][0] += c[p] * (depth[p] - depth[*j]);
		for (int l = 1; l <= k; l++)
			f[p][*j][l] = min(f[p][*j][l] + c[p] * (depth[p] - depth[*j]), g[p][*j][l - 1]);
	}
	st.pop_back();
	return;
}

int main()
{
	// freopen("riv.in", "r", stdin);
	// freopen("riv.out", "w", stdout);
	scanf("%d%d", &n, &k);
	graph.resize(n + 1);
	for (int i = 1; i <= n; i++)
	{
		int fa, dist;
		scanf("%d%d%d", c + i, &fa, &dist);
		graph[fa].push_back((edge){i, dist});
	}
	dfs(0);
	printf("%d\n", f[0][0][k]);
	return 0;
}
posted @ 2020-06-12 06:43  Macesuted  阅读(91)  评论(0编辑  收藏  举报