题意:一棵n(n<=500)个节点的有根树,树的边有正整数权,表示两个节点之间的距离,你的任务是回答这样的询问,从根节点出发,走不超过x(x<=5000000)单位的距离,最多能走多少个节点,节点经过多次算一个,对于每次的询问(群文次数小于1000)输出:经过节点数最大的值。 注意题目给出的 i, j,d,其中 j 是 i 的父节点。
这道题想了两天,看了题解一开始也不明白(泪目),后来一想可以当成树上的01背包来做,终于ac,这应该是目前做的最难的一道动态规划题了。
思路是:这道题的难点之一是可以返回父节点,而且由于x太大,状态里假如有x那么数组太大开不下,我们要另外选择一种方式表示状态,对此我们可以用两个状态来表示,用d[i][x][j+k][0]表示在以x结点为根结点的树中,从前i颗子树中访问j+k个结点且返回到x结点(返回用零表示)所需要的最小花费;d[i][x][j+k][1]类似,只是不用返回到x结点(用1表示),这其实就有点类似于零一背包了,但这样也有问题,数组还是太大,开不下,这时就要让数组滚动起来,用son数组记录x结点的孩子节点总数,枚举son时从大到小,这样可以把状态的第一位省掉,那么我们就可以得到状态方程了
d[x][j+k][0] = min(d[x][j + k][0], d[x][j][0] + d[t.to][k][0] + t.val * 2);//t表示x的孩子节点,val代表花费
d[x][j+k][1] = min(d[x][j + k][1], min(d[x][j][0] + d[t.to][k][1] + t.val, d[x][j][1] + d[t.to][k][0] + 2 * t.val));
#include<cstdio> #include<cstring> #include<cmath> #include<cstdlib> #include<iostream> #include<algorithm> #include<vector> #include<map> #include<queue> #include<stack> #include<string> #include<map> #include<set> using namespace std; #define LL long long const int maxn = 500 + 10; const int maxx = 5000000 + 100; const int INF = 0x3f3f3f3f; int d[maxn][maxn][2], pa[maxn], son[maxn]; struct Edge { int from, to, val; }; vector<int> G[maxn]; vector<Edge> edges; void dfs(int x) { d[x][1][0] = d[x][1][1] = 0; son[x] = 1; for(int i = 0; i < G[x].size(); i++) { Edge t = edges[G[x][i]]; dfs(t.to); for(int j = son[x]; j >= 1; j--) { for(int k = 1; k <= son[t.to]; k++) { d[x][j+k][0] = min(d[x][j + k][0], d[x][j][0] + d[t.to][k][0] + t.val * 2); d[x][j+k][1] = min(d[x][j + k][1], min(d[x][j][0] + d[t.to][k][1] + t.val, d[x][j][1] + d[t.to][k][0] + 2 * t.val)); } } son[x] += son[t.to]; } } int main() { //freopen("input.txt", "r", stdin); int n, kase = 0; while(scanf("%d", &n) == 1 && n) { edges.clear(); for(int i = 0; i < n; i++) G[i].clear(); memset(pa, -1, sizeof(pa)); memset(d, INF, sizeof(d)); for(int k = 0; k < n - 1; k++) { int i, j, val; scanf("%d%d%d", &i, &j, &val); edges.push_back((Edge){ j, i, val}); G[j].push_back(edges.size() - 1); pa[i] = j; } int root = 0; while(pa[root] != -1) root = pa[root]; dfs(root); int Q; scanf("%d", &Q); printf("Case %d:\n", ++kase); while(Q--) { int x; scanf("%d", &x); int ans = 1; for(int i = 1; i <= n; i++) { if(x >= d[root][i][0]) ans = i; else if(x >= d[root][i][1]) ans = i; else break; } printf("%d\n", ans); } // for(int i = 1; i <= n; i++) printf("%d %d\n", d[root][i][0], d[root][i][1]); } return 0; }
uvalive 4015 洞穴cave(树的dp/01背包)
原文地址:http://blog.csdn.net/u014664226/article/details/46137351