题目链接: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;
}