题目链接:12223 - Moving to Nuremberg
题目大意:给定一颗无根树,有一些结点需要访问num次。然后你现在选择一个点作为起点,去访问每个点,访问完要回到原点,求一个起点,使得访问完所有点的路程最少,问这个路程,并求出这些点(如果有多个点一样小都要输出)。
思路:这题是由父亲结点u的状态去推出子节点v状态。如图:
dp[u]为结点u的最佳方案,num[v]为根节点v的子树的总次数,那么要由dp[u]去推出dp[v],那么g[u][v]这条路对于v后面的点(图中蓝色点)一共会少走num[v] * g[u][v] * 2,对于v前面的点(图中红色点)一共会多走(sum(总次数) - num[v]) * g[u][v] * 2;所以状态转移方程为:dp[v] = dp[u] + (sum - num[v]) * w - num[v] * w; (这里w是路径的两倍)。
然后要进行两遍dfs,第一遍预处理是num数组,并且求出dp[1]的值,第二遍就是树形DP。
代码:
#include <stdio.h> #include <string.h> #include <vector> using namespace std; #define min(a,b) ((a)<(b)?(a):(b)) const int N = 50005; int t, n, m, vis[N]; typedef long long ll; ll dp[N], num[N], minv, sum; struct Node { int v; ll w; Node(){} Node(int vv, ll ww) { v = vv; w = ww; } }; vector<Node> g[N]; void dfs1(int u, ll len) { vis[u] = 1; dp[1] += len * num[u]; for (int i = 0; i < g[u].size(); i++) { ll w = g[u][i].w * 2; int v = g[u][i].v; if (vis[v]) continue; dfs1(v, len + w); num[u] += num[v]; } } void dfs2(int u) { vis[u] = 1; minv = min(dp[u], minv); for (int i = 0; i < g[u].size(); i++) { ll w = g[u][i].w * 2; int v = g[u][i].v; if (vis[v]) continue; dp[v] = dp[u] + (sum - num[v]) * w - num[v] * w; dfs2(v); } } int main() { scanf("%d", &t); while (t--) { memset(vis, 0, sizeof(vis)); memset(g, 0, sizeof(g)); dp[1] = 0; sum = 0; scanf("%d", &n); int u, v; ll w; for (int i = 1; i < n; i++) { scanf("%d%d%lld", &u, &v, &w); g[u].push_back(Node(v, w)); g[v].push_back(Node(u, w)); } scanf("%d", &m); memset(num, 0, sizeof(num)); while (m--) { scanf("%d%lld", &u, &w); sum += w; num[u] = w; } dfs1(1, 0); memset(vis, 0, sizeof(vis)); minv = dp[1]; dfs2(1); printf("%lld\n", minv); int bo = 0; for (u = 1; u <= n; u++) { if (dp[u] == minv) { if (bo++) printf(" "); printf("%d", u); } } printf("\n"); } return 0; }
UVA 12223 - Moving to Nuremberg(树形DP),布布扣,bubuko.com
UVA 12223 - Moving to Nuremberg(树形DP)
原文:http://blog.csdn.net/accelerator_/article/details/21299055