洛谷树形DP前22题(基础)
xzy的树形dp题单 - 题单 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
P1122 最大子树和 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
题意:对一棵树进行删边,判断删完边之后连通块最大权值之和是多少?
定义 f [ c u r ] f[cur] f[cur]为以 c u r cur cur为根的子树权值之和最大值
初始值 f [ c u r ] = w [ c u r ] f[cur] = w[cur] f[cur]=w[cur]
如果他的其中一棵子树大于0,则选择这棵子树。 f[cur] += max(f[p], 0);
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXN = 16100;
int n, id;
int w[MAXN], head[MAXN], f[MAXN], ans = -2147483647;
struct Edge{
int to, next;
}E[MAXN * 2];
void addedge(int u, int v)
{
E[id].to = v;
E[id].next = head[u];
head[u] = id++;
return ;
}
void dfs(int cur, int father)
{
for(int i = head[cur]; i != -1; i = E[i].next)
{
int p = E[i].to;
if(p == father) continue;
dfs(p, cur);
f[cur] += max(f[p], 0);
}
}
int main()
{
memset(head, -1, sizeof(head));
scanf("%d", &n);
for(int i = 1; i <= n; i++) {
scanf("%d", &w[i]);
f[i] = w[i];
}
for(int i = 1; i < n; i++)
{
int u, v;
scanf("%d%d", &u, &v);
addedge(u, v);
addedge(v, u);
}
dfs(1, -1);
int ans = -2147483647;
for(int i = 1; i <= n; i++) ans = max(ans, f[i]);
printf("%d\n", ans);
return 0;
}
P1352 没有上司的舞会 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
树形DP入门题
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXN = 6e3 + 60;
int n, id;
int w[MAXN], head[MAXN], cnt[MAXN], f[MAXN][2];
struct Edge{
int to, next;
}E[MAXN * 2];
void addedge(int u, int v)
{
E[id].to = v;
E[id].next = head[u];
head[u] = id++;
return;
}
void dfs(int cur, int father)
{
f[cur][0] = 0;
f[cur][1] = w[cur];
for(int i = head[cur]; i != -1; i = E[i].next)
{
int p = E[i].to;
if(p == father) continue;
dfs(p, cur);
f[cur][0] += max(f[p][0], f[p][1]);
f[cur][1] += f[p][0];
}
}
int main()
{
memset(head, -1, sizeof(head));
scanf("%d", &n);
for(int i = 1; i <= n; i++) scanf("%d", &w[i]);
for(int i = 1; i < n; i++)
{
int u, v;
cin >> u >> v;
addedge(u, v);
addedge(v, u);
cnt[u]++;
cnt[v]++;
}
int root = -1;
for(int i = 1; i <= n; i++)
{
if(cnt[i] == 1) {
root = i;
break;
}
}
dfs(root, -1);
int ans = max(f[root][0], f[root][1]);
printf("%d\n", ans);
return 0;
}
P2015 二叉苹果树 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
树形背包
f [ i ] [ j ] [ k ] f[i][j][k] f[i][j][k]表示以 i i i为子树, j j j为考虑了 j j j个节点,已经选了 k k k条边的最大价值。(p为其子节点)
f [ u ] [ j ] [ k ] = m a x ( f [ u ] [ j − 1 ] [ x ] + f [ p ] [ p 子 结 点 的 数 量 ] [ k − x − 1 ] + w ) f[u][j][k]=max(f[u][j-1][x]+f[p][p子结点的数量][k-x-1]+w) f[u][j][k]=max(f[u][j−1][x]+f[p][p子结点的数量][k−x−1]+w)
可以考虑优化掉中间一维
f [ i ] [ j ] f[i][j] f[i][j]表示以 i i i为子树的结点有 j j j条边。
考虑到一棵树上的结点 u u u与其子节点。
d p [ u ] [ j ] = m a x ( d p [ u ] [ k ] + d p [ p ] [ j − k − 1 ] + w ) dp[u][j] = max(dp[u][k] + dp[p][j - k - 1]+w) dp[u][j]=max(dp[u][k]+dp[p][j−k−1]+w)
这是很明显的树形背包。
注意枚举背包容量的时候需要逆序枚举(相当于01背包的优化)(先枚举背包容量,再枚举决策)
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXN = 105;
int n, q, id, ans;
int head[MAXN], f[MAXN][MAXN];
struct Edge{
int to, next, w;
}E[MAXN * 2];
void addedge(int u, int v, int w)
{
E[id].to = v;
E[id].w = w;
E[id].next = head[u];
head[u] = id++;
return;
}
void dfs(int u, int fa)
{
f[u][0] = 0;
for(int i = head[u]; i != -1; i = E[i].next)
{
int p = E[i].to, w = E[i].w;
if(p == fa) continue;
dfs(p, u);
for(int i = q; i >= 1; i--)
{
for(int j = 0; j < i; j++)
{
f[u][i] = max(f[u][i], f[u][j] + f[p][i - j - 1] + w);
}
}
}
}
int main()
{
memset(head, -1, sizeof(head));
scanf("%d%d", &n, &q);
for(int i = 1; i < n; i++)
{
int u, v, w;
scanf("%d%d%d", &u, &v, &w);
addedge(u, v, w);
addedge(v, u, w);
}
dfs(1, -1);
ans = f[1][q];
/*
for(int i = 1; i <= n; i++)
{
for(int j = 1; j <= q; j++)
printf("%d ", f[i][j]);
printf("\n");
}*/
printf("%d\n", ans);
return 0;
}
[P2014 CTSC1997]选课 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
同上题
题意:选课就是有依赖的树形背包只有父节点被选,子节点才能被选择。
与上一题不同的是此道题会出现多棵树
解决办法:添加一个0号结点,0号结点权值为0必选得选择。为所有根节点的父节点。就可以将多棵树的问题转化到一棵树上解决。
最后答案求选m门课,即是 f [ 0 ] [ m + 1 ] f[0][m+1] f[0][m+1]的值
f [ u ] [ k ] = m a x ( f [ u ] [ j ] + f [ p ] [ k − j ] ) f[u][k] = max(f[u][j]+f[p][k-j]) f[u][k]=max(f[u][j]+f[p][k−j])
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXN = 305;
int n, m, id;
int k[MAXN], head[MAXN], f[MAXN][MAXN];
struct Edge{
int to, next;
}E[MAXN * 2];
void addedge(int u, int v)
{
E[id].next = head[u];
E[id].to = v;
head[u] = id++;
return;
}
void dfs(int u, int fa)
{
f[u][1] = k[u];
for(int i = head[u]; i != -1; i = E[i].next)
{
int p = E[i].to;
if(p == fa) continue;
dfs(p, u);
for(int i = m + 1; i >= 1; i--)
{
for(int j = 1; j <= i; j++)
{
f[u][i] = max(f[u][i], f[u][j] + f[p][i - j]);
}
}
}
}
int main()
{
memset(head, -1, sizeof(head));
scanf("%d%d", &n, &m);
k[0] = 0;
for(int i = 1; i <= n; i++)
{
int s;
scanf("%d%d", &s, &k[i]);
addedge(i, s);
addedge(s, i);
}
dfs(0, -1);
/*
for(int i = 0; i <= n; i++)
{
for(int j = 0; j <= m + 1; j++)
{
printf("%d ", f[i][j]);
}
printf("\n");
}
*/
printf("%d\n", f[0][m + 1]);
return 0;
}
P2016 战略游戏 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
树上最小点覆盖
f[u][0] += f[p][1];
f[u][1] += min(f[p][0], f[p][1]);
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXN = 1590;
int n, id;
int head[MAXN], f[MAXN][2];
struct Edge{
int to, next;
}E[MAXN * 2];
void addedge(int u, int v)
{
E[id].to = v;
E[id].next = head[u];
head[u] = id++;
}
void dfs(int u, int fa)
{
f[u][0] = 0;
f[u][1] = 1;
for(int i = head[u]; i != -1; i = E[i].next)
{
int p = E[i].to;
if(p == fa) continue;
dfs(p, u);
f[u][0] += f[p][1];
f[u][1] += min(f[p][0], f[p][1]);
}
}
int main()
{
memset(head, -1, sizeof(head));
cin >> n;
for(int i = 1; i <= n; i++)
{
int x, num;
cin >> x >> num;
for(int j = 1; j <= num; j++) {
int u;
cin >> u;
addedge(u, x);
addedge(x, u);
}
}
dfs(1, -1);
printf("%d\n", min(f[1][0], f[1][1]));
return 0;
}
[P3478 POI2008]STA-Station - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)(换根DP)
题意:给定一棵树,求以树中某个点为根结点的所有结点深度之和
换根DP思想:第一遍dfs可以求出根节点的信息,第二遍dfs根据父节点的信息推出子节点的信息。
第一次dfs可以求出根节点的所有点深度值和(求深度为顺序往下)(以当前点为根的子结点个数是反dfs序)
考虑这么一件事:如果把当前根节点的子节点作为根,则以当前子节点为根的点(含自身)深度-1,其他点深度+1
这个顺序是从上往下更改的
f [ p ] = f [ u ] − s z [ p ] + n − s z [ p ] f[p]=f[u]-sz[p]+n-sz[p] f[p]=f[u]−sz[p]+n−sz[p]
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int MAXN = 1e6 + 60;
int n, id;
ll head[MAXN], f[MAXN], depth[MAXN], sz[MAXN];
struct Edge{
int to, next;
}E[MAXN * 2];
void addedge(int u, int v)
{
E[id].to = v;
E[id].next = head[u];
head[u] = id++;
return;
}
void dfs1(int u, int fa)
{
sz[u] = 1;
depth[u] = depth[fa] + 1;
for(int i = head[u]; i != -1; i = E[i].next)
{
int p = E[i].to;
if(p == fa) continue;
dfs1(p, u);
sz[u] += sz[p];
}
}
void dfs2(int u, int fa)
{
for(int i = head[u]; i != -1; i = E[i].next)
{
int p = E[i].to;
if(p == fa) continue;
f[p] = f[u] - sz[p] + (n - sz[p]);
dfs2(p, u);
}
}
int main()
{
memset(head, -1, sizeof(head));
cin >> n;
for(int i = 1; i < n; i++) {
int u, v;
cin >> u >> v;
addedge(u, v);
addedge(v, u);
}
dfs1(1, 0);
for(int i = 1; i <= n; i++) f[1] += depth[i];
dfs2(1, 0);
ll ans = 0;
int res;
for(int i = 1; i <= n; i++) {
if(ans < f[i]) {
ans = f[i];
res = i;
}
}
printf("%d\n", res);
return 0;
}
[P1040 NOIP2003 提高组] 加分二叉树 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
区间DP
对于一个序列,它左边的数构成了它的左子树,它右边的数构成了它的右子树。
f [ l ] [ r ] = f [ l ] [ k − 1 ] ∗ f [ k + 1 ] [ r ] + f [ k ] [ k ] f[l][r]=f[l][k-1]*f[k+1][r]+f[k][k] f[l][r]=f[l][k−1]∗f[k+1][r]+f[k][k]
注意左子树与右子树为空的情况
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int MAXN = 32;
int n;
ll w[MAXN], f[MAXN][MAXN], root[MAXN][MAXN];
void print(int l, int r)
{
if(l > r) return;
int ro = root[l][r];
printf("%lld ", ro);
print(l, ro - 1);
print(ro + 1, r);
}
int main()
{
cin >> n;
for(int i = 1; i <= n; i++) {
scanf("%lld", &w[i]);
f[i][i] = w[i];
root[i][i] = i;
}
for(int len = 2; len <= n; len ++)
{
for(int l = 1; l + len - 1 <= n; l ++)
{
int r = l + len - 1;
for(int k = l; k <= r; k ++)
{
if(k == l) {
if(f[l][r] < f[l + 1][r] + f[l][l])
{
f[l][r] = f[l + 1][r] + f[l][l];
root[l][r] = l;
}
}
else if(k == r) {
if(f[l][r] < f[l][r - 1] + f[r][r])
{
f[l][r] = max(f[l][r], f[l][r - 1] + f[r][r]);
root[l][r] = r;
}
}
else {
if(f[l][r] < f[l][k - 1] * f[k + 1][r] + f[k][k])
{
f[l][r] = f[l][k - 1] * f[k + 1][r] + f[k][k];
root[l][r] = k;
}
}
}
}
}
printf("%lld\n", f[1][n]);
print(1, n);
cout << endl;
return 0;
}
CF219D Choosing Capital for Treeland - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)(换根DP)
题意:给定一棵树,所有边都是单向边。首都定义为到其他点都有一条路径。问选取哪一个点为首都,能使翻转的边数最少。
存图的时候存双向边,存在的边设为1,不存在设为0。
第一次dfs的时候,从下到上计算结点1所要的反转的边是多少。
第二次dfs的时候,从上往下,判断当前子节点与父节点连的边的方向,来确定 f [ p ] = f [ u ] − 1 或 是 f [ u ] + 1 f[p] = f[u]-1或是f[u]+1 f[p]=f[u]−1或是f[u]+1
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXN = 2e5 + 50;
int n, id;
int head[MAXN], f[MAXN];
struct Edge{
int to, next, w;
}E[MAXN * 2];
void addedge(int u, int v, int w)
{
E[id].to = v;
E[id].w = w;
E[id].next = head[u];
head[u] = id++;
return;
}
void dfs1(int u, int fa)
{
for(int i = head[u]; i != -1; i = E[i].next)
{
int p = E[i].to, w = E[i].w;
if(p == fa) continue;
dfs1(p, u);
f[u] += (f[p] + w);
}
}
void dfs2(int u, int fa)
{
for(int i = head[u]; i != -1; i = E[i].next)
{
int p = E[i].to, w = E[i].w;
if(p == fa) continue;
if(w == 0) f[p] = f[u] + 1;
else f[p] = f[u] - 1;
dfs2(p, u);
}
}
int main()
{
memset(head, -1, sizeof(head));
scanf("%d", &n);
for(int i = 1; i < n; i++)
{
int u, v;
cin >> u >> v;
addedge(u, v, 0);
addedge(v, u, 1);
}
dfs1(1, -1);
// printf("%d\n", f[1]);
dfs2(1, -1);
int minn = f[1];
for(int i = 1; i <= n; i++) minn = min(minn, f[i]);
printf("%d\n", minn);
for(int i = 1; i <= n; i++) {
if(minn == f[i]) printf("%d ", i);
}
printf("\n");
return 0;
}
P1270 “访问”美术馆 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
题意:一个画廊,从大厅开始,通过每一条边都需要一定的时间,画廊里面有画,拿一幅画需要5s,问在规定时间内最多能拿走多少张画。
树形背包的问题:
数组要开的足够大。
小偷最后需要从大门出去,所以通过每条路的时间为 t i m e ∗ 2 time*2 time∗2
不能恰好在规定时间内出去,规定时间-1。
难度在于建图:
采用的是递归建图的方式
如果有画都让当前节点与0相连,初始化的时候只需让 f [ 0 ] [ 0 ] = 1 f[0][0] = 1 f[0][0]=1
树形背包 f [ u ] [ j ] f[u][j] f[u][j]表示从到 u u u个结点,花了 j j j秒钟,偷画的最大数量
同样是逆序枚举
f [ u ] [ j ] = m a x ( f [ u ] [ j − k − w ] + f [ p ] [ k ] ) f[u][j] = max(f[u][j-k-w]+f[p][k]) f[u][j]=max(f[u][j−k−w]+f[p][k])
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int M = 105, N = 2010;
int s, id, tot, root;
int head[N], f[N][N];
struct Edge{
int to, next, w;
}E[M * 2];
void addedge(int u, int v, int w)
{
E[id].to = v;
E[id].w = w;
E[id].next = head[u];
head[u] = id++;
return;
}
void input(int u, int fa)
{
int time, num;
scanf("%d%d", &time, &num);
addedge(fa, u, time * 2);
if(!num)
{
input(++tot, u);
input(++tot, u);
}
else {
while(num)
{
addedge(u, 0, 5);
num--;
}
}
}
void dfs(int u, int fa)
{
for(int i = head[u]; i != -1; i = E[i].next)
{
int p = E[i].to, w= E[i].w;
if(p == fa) continue;
dfs(p, u);
for(int j = s; j >= w; j --)
{
for(int k = 0; k <= j - w; k++)
{
f[u][j] = max(f[u][j], f[u][j - k - w] + f[p][k]);
}
}
}
}
int main()
{
memset(head, -1, sizeof(head));
cin >> s;
s -- ;
root = tot = 1;
input(++tot, root);
f[0][0] = 1;
dfs(root, -1);
printf("%d\n", f[root][s]);
return 0;
}
[P1131 ZJOI2007] 时态同步 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
调整子树的时候,调整父节点比调整子节点可以用更少的道具
因此对于每个节点 u u u只需要判断,离它最远的子节点的距离为多少,最后自底向上进行更新。
记得开long long
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int MAXN = 5e5 + 50;
ll head[MAXN], f[MAXN];
ll n, root, id, ans;
struct Edge{
int to, next, w;
}E[MAXN * 2];
void addedge(int u, int v, int w)
{
E[id].w = w;
E[id].to = v;
E[id].next = head[u];
head[u] = id++;
return;
}
void dfs(int u, int fa)
{
for(int i = head[u]; i != -1; i = E[i].next)
{
int p = E[i].to, w = E[i].w;
if(p == fa) continue;
dfs(p, u);
f[u] = max(f[u], f[p] + w);
}
for(int i = head[u]; i != -1; i = E[i].next)
{
int p = E[i].to, w = E[i].w;
if(p == fa) continue;
ans += f[u] - (f[p] + w);
}
}
int main()
{
memset(head, -1, sizeof(head));
scanf("%d%d", &n, &root);
for(int i = 1; i < n; i++)
{
int u, v, w;
scanf("%d%d%d", &u, &v, &w);
addedge(u, v, w);
addedge(v, u, w);
}
dfs(root, -1);
cout << ans << endl;
return 0;
}
[P2279 HNOI2003]消防局的设立 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
一个点可以覆盖与他距离为二的两个点,问一棵树上最少多少点能覆盖整棵树
自底向上进行贪心,每次都覆盖暂未覆盖结点的爷爷结点(用一个优先队列来维护)
第一次dfs求出深度,第二次dfs模拟每一次覆盖。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
typedef pair<int, int> PII;
const int MAXN = 1010;
int n, id;
int head[MAXN], f[MAXN], d[MAXN];
bool vis[MAXN];
struct Edge{
int to, next;
}E[MAXN * 2];
priority_queue<PII>pq;
void addedge(int u, int v)
{
E[id].to = v;
E[id].next = head[u];
head[u] = id++;
}
void dfs1(int u, int fa)
{
d[u] = d[fa] + 1;
pq.push(make_pair(d[u], u));
for(int i = head[u]; i != -1; i = E[i].next)
{
int p = E[i].to;
if(p == fa) continue;
dfs1(p, u);
}
}
void dfs2(int u, int d)
{
if(d > 2) return;
vis[u] = true;
for(int i = head[u]; i != -1; i = E[i].next)
{
int p = E[i].to;
dfs2(p, d + 1);
}
}
int main()
{
memset(head, -1, sizeof(head));
scanf("%d", &n);
for(int i = 2; i <= n; i++)
{
scanf("%d", &f[i]);
addedge(i, f[i]);
addedge(f[i], i);
}
dfs1(1, -1);
memset(vis, false, sizeof(vis));
int ans = 0;
while(!pq.empty())
{
while(!pq.empty() && vis[pq.top().second]) pq.pop();
if(pq.empty()) break;
auto u = pq.top();
int d = u.first, id = u.second;
dfs2(f[f[id]], 0);
pq.pop();
ans++;
}
printf("%d\n", ans);
return 0;
}
貌似还有一种树形DP状态机的思想
P1273 有线电视网 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)(树形背包)
d p [ u ] [ i ] [ j ] dp[u][i][j] dp[u][i][j]表示第 u u u个结点,考虑其前 i i i个儿子,联通 j j j个居民最大的收益。
d p [ u ] [ i ] [ j ] = m a x ( d p [ u ] [ i ] [ j ] , d p [ u ] [ i − 1 ] [ j − k ] + d p [ v ] [ f u l l s o n [ v ] ] [ k ] − w ) dp[u][i][j] = max(dp[u][i][j], dp[u][i-1][j-k] + dp[v][fullson[v]][k]-w) dp[u][i][j]=max(dp[u][i][j],dp[u][i−1][j−k]+dp[v][fullson[v]][k]−w)
背包逆序枚举优化成二维
d p [ u ] [ j ] = m a x ( d p [ u ] [ j ] , d p [ u ] [ j − k ] + d p [ v ] [ k ] − w ) dp[u][j] = max(dp[u][j], dp[u][j-k]+dp[v][k]-w) dp[u][j]=max(dp[u][j],dp[u][j−k]+dp[v][k]−w)
注意:
这种树形背包容量的上限为子节点的个数(需要函数中返回(设置成int类型,返回其子结点的个数))
枚举决策的时候是子树大小 t t t与背包空间 j j j取min,即 m i n ( t , j ) min(t,j) min(t,j)
叶结点需要单独考虑
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXN = 3060;
int n, m, id;
int head[MAXN], ww[MAXN], dp[MAXN][MAXN];
struct Edge{
int to, next, w;
}E[MAXN * 2];
void addedge(int u, int v, int w)
{
E[id].w = w;
E[id].to = v;
E[id].next = head[u];
head[u] = id++;
}
int dfs(int u, int fa)
{
if(u > n - m)
{
dp[u][0] = 0;
dp[u][1] = ww[u];
return 1;
}
dp[u][0] = 0;
int leaf = 0;
for(int i = head[u]; i != -1; i = E[i].next)
{
int p = E[i].to, w = E[i].w;
if(p == fa) continue;
int t = dfs(p, u);
leaf += t;
for(int j = leaf; j >= 0; j --)
{
for(int k = 0; k <= min(j, t); k++)
{
dp[u][j] = max(dp[u][j], dp[u][j - k] + dp[p][k] - w);
}
}
}
return leaf;
}
int main()
{
memset(head, -1, sizeof(head));
memset(dp, -0x3f, sizeof(dp));
cin >> n >> m;
for(int i = 1; i <= n - m; i++)
{
int num;
cin >> num;
for(int j = 1; j <= num; j++)
{
int v, w;
cin >> v >> w;
addedge(i, v, w);
addedge(v, i, w);
}
}
for(int j = n - m + 1; j <= n; j++) cin >> ww[j];
dfs(1, -1);
int ans = -1;
for(int i = m; i >= 0; i--)
{
if(dp[1][i] >= 0) {
ans = i;
printf("%d\n", ans);
break;
}
}
return 0;
}
[P2899 USACO08JAN]Cell Phone Network G - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)(状态机)
题意:覆盖一棵树,对于任意一个点只要保证他的父节点,子节点,他自己有一个点被覆盖即可。
状态机思想
d p [ u ] [ 0 ] dp[u][0] dp[u][0],表示自己本身被覆盖
d p [ u ] [ 1 ] dp[u][1] dp[u][1],表示它的子节点被覆盖
d p [ u ] [ 2 ] dp[u][2] dp[u][2],表示它的父节点被覆盖
dp[u][0] = sum(min(dp[p][0], dp[p][1], dp[p][2]));
dp[u][1] = dp[x][0] + sum(min(dp[p][0], dp[p][1]));
dp[u][2] = sum(min(dp[p][0], dp[p][1]));
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXN = 10010;
int n, m, id;
int head[MAXN], ww[MAXN], dp[MAXN][3];
struct Edge{
int to, next;
}E[MAXN * 2];
void addedge(int u, int v)
{
E[id].to = v;
E[id].next = head[u];
head[u] = id++;
}
void dfs(int u, int fa)
{
int sum = 0;
dp[u][0] = 1;
for(int i = head[u]; i != -1; i = E[i].next)
{
int p = E[i].to;
if(p == fa) continue;
dfs(p, u);
dp[u][0] += min(dp[p][0], min(dp[p][1], dp[p][2]));
dp[u][2] += min(dp[p][0], dp[p][1]);
sum += min(dp[p][0], dp[p][1]);
}
dp[u][1] = 1e9;
for(int i = head[u]; i != -1; i = E[i].next)
{
int p = E[i].to;
dp[u][1] = min(dp[u][1], sum - min(dp[p][1], dp[p][0]) + dp[p][0]);
}
}
int main()
{
memset(head, -1, sizeof(head));
cin >> n;
for(int i = 1; i < n; i++)
{
int u, v;
cin >> u >> v;
addedge(u, v);
addedge(v, u);
}
dfs(1, -1);
printf("%d\n", min(dp[1][0], dp[1][1]));
return 0;
}
[P4084 USACO17DEC]Barn Painting G - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
给一棵树染色,已经有点被染过色。
d p [ i ] [ 1 / 2 / 3 ] dp[i][1/2/3] dp[i][1/2/3]表示第 i i i个结点,被染成第1/2/3种颜色。
dp[i][1] *= (dp[p][2] + dp[p][3])
dp[i][2] *= (dp[p][1] + dp[p][3])
dp[i][3] *= (dp[p][1] + dp[p][2])
注意初始化
被染过色的只有 d p [ u ] [ c o l o r [ u ] ] = 1 dp[u][color[u]]=1 dp[u][color[u]]=1
没被染过色的都是1
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
typedef long long ll;
const int MAXN = 100100, mod = 1e9 + 7;
int n, m, id;
ll ans;
ll head[MAXN], dp[MAXN][4], color[MAXN];
struct Edge{
int to, next;
}E[MAXN * 2];
void addedge(int u, int v)
{
E[id].to = v;
E[id].next = head[u];
head[u] = id++;
}
void dfs(int u, int fa)
{
if(color[u])
{
dp[u][color[u]] = 1;
}
else {
for(int i = 1; i <= 3; i++)
{
dp[u][i] = 1;
}
}
for(int i = head[u]; i != -1; i = E[i].next)
{
int p = E[i].to;
if(p == fa) continue;
dfs(p, u);
dp[u][1] = dp[u][1] * ((dp[p][2] + dp[p][3]) % mod) % mod;
dp[u][2] = dp[u][2] * ((dp[p][1] + dp[p][3]) % mod) % mod;
dp[u][3] = dp[u][3] * ((dp[p][1] + dp[p][2]) % mod) % mod;
}
}
int main()
{
memset(head, -1, sizeof(head));
cin >> n >> m;
for(int i = 1; i < n; i++)
{
int u, v;
cin >> u >> v;
addedge(u, v);
addedge(v, u);
}
for(int i = 1; i <= m; i++)
{
int k, c;
cin >> k >> c;
color[k] = c;
}
dfs(1, -1);
ans = (dp[1][1] + dp[1][2] + dp[1][3]) % mod;
printf("%lld\n", ans);
return 0;
}
[P2986 USACO10MAR]Great Cow Gathering G - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)(换根DP)
先求出1号结点的路径之和
然后根据 f [ p ] = f [ u ] − s z [ p ] ∗ w + ( c n t − s z [ p ] ) ∗ w f[p]=f[u]-sz[p]*w+(cnt-sz[p])*w f[p]=f[u]−sz[p]∗w+(cnt−sz[p])∗w
遍历所有最小值得出答案
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
typedef long long ll;
const int MAXN = 100100, mod = 1e9 + 7;
ll n, m, id, cnt;
ll head[MAXN], f[MAXN], num[MAXN], sz[MAXN];
struct Edge{
int to, next, w;
}E[MAXN * 2];
void addedge(int u, int v, int w)
{
E[id].to = v;
E[id].next = head[u];
E[id].w = w;
head[u] = id++;
}
void dfs1(int u, int fa)
{
sz[u] = num[u];
for(int i = head[u]; i != -1; i = E[i].next)
{
int p = E[i].to, w = E[i].w;
if(p == fa) continue;
dfs1(p, u);
sz[u] += sz[p];
f[u] += (f[p] + sz[p] * w);
}
}
void dfs2(int u, int fa)
{
for(int i = head[u]; i != -1; i = E[i].next)
{
int p = E[i].to, w = E[i].w;
if(p == fa) continue;
f[p] = f[u] - sz[p] * w + (cnt - sz[p]) * w;
dfs2(p, u);
}
}
int main()
{
memset(head, -1, sizeof(head));
cin >> n;
for(int i = 1; i <= n; i++) {
scanf("%lld", &num[i]);
cnt += num[i];
}
for(int i = 1; i < n; i++)
{
int u, v, w;
scanf("%d%d%d", &u, &v, &w);
addedge(u, v, w);
addedge(v, u, w);
}
dfs1(1, -1);
dfs2(1, -1);
ll ans = f[1];
for(int i = 1; i <= n; i++) ans = min(ans, f[i]);
cout << ans << endl;
return 0;
}
[P4438 HNOI/AHOI2018]道路 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
先读题。
这个题给出了一棵树,在这棵树里,乡村的点都没有儿子,而城市的点都有两个儿子。所以?
很据题意,可以设 f i , j , k f_{i,j,k} fi,j,k,表示从根节点走到序号为 i 的点,通过了 j 条向左走的未修的路(走向左儿子的路),以及 k 条向右走的未修的路(走向右儿子的路)时,以 i 为节点的子树的最小不便利值。
这个数组一开出来,就有浓厚的 DP气息了。 当 i 是一个乡村时,显然能直接求出 f i , j , k = c i ( a i + j ) ( b i + k ) f_{i,j,k}=c_i(a_i+j)(b_i+k) fi,j,k=ci(ai+j)(bi+k) 。而ii是一个城市,则设 l i l_i li为 i 的左儿子, r i r_i ri 为 i 的右儿子。有两种情况:
1.修向左走的路。则有:
f i , j , k = f l i , j , k + f r i , j , k + 1 f_{i,j,k}=f_{l_i,j,k}+f_{r_i,j,k+1} fi,j,k=fli,j,k+fri,j,k+1
2.修向右走的路。则有:
f i , j , k = f l i , j + 1 , k + f r i , j , k f_{i,j,k}=f_{l_i,j+1,k}+f_{r_i,j,k} fi,j,k=fli,j+1,k+fri,j,k
两者取其 min ,方能得解。
最后的答案?当然是 f 1 , 0 , 0 f_{1,0,0} f1,0,0 了。
#include<cstdio>
#include<cstring>
#include<algorithm>
#define LL long long
using namespace std;
int n;
struct node{LL x,y,z;} a[20010];
int son[20010][5];
LL f[20010][45][45];
LL dfs(int x,int p,int q)
{
if(x>=n) return a[x-n+1].z*(a[x-n+1].x+p)*(a[x-n+1].y+q);
if(f[x][p][q]!=f[n+1][41][41]) return f[x][p][q];
return f[x][p][q]=min(dfs(son[x][0],p,q)+dfs(son[x][1],p,q+1),dfs(son[x][1],p,q)+dfs(son[x][0],p+1,q));
}
int main()
{
int x,y;
scanf("%d",&n);
memset(f,63,sizeof(f));
for(int i=1;i<n;i++)
{
scanf("%d %d",&x,&y);
if(x<0) x=-x+n-1;
if(y<0) y=-y+n-1;
son[i][0]=x;
son[i][1]=y;
}
for(int i=1;i<=n;i++)
scanf("%lld %lld %lld",&a[i].x,&a[i].y,&a[i].z);
printf("%lld",dfs(1,0,0));
}
[P3047 USACO12FEB]Nearby Cows G - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)(换根DP)
给你一棵 n个点的树,点带权,对于每个节点求出距离它不超过 k 的所有节点权值和 m i m_i mi
f [ u ] [ i ] f[u][i] f[u][i]表示结点 u u u,并且距离为 i i i的权值之和
dfs1计算根节点的权值
dfs2根据容斥原理计算,距离子节点 p p p距离为 x x x的点是距离父节点 u u u距离为 x − 1 x-1 x−1的点,但是 p p p的子树被重复计算。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
typedef long long ll;
const int MAXN = 100100, mod = 1e9 + 7;
int n, m, id, cnt;
int head[MAXN], f[MAXN][25], c[MAXN], d[MAXN];
struct Edge{
int to, next;
}E[MAXN * 2];
void addedge(int u, int v)
{
E[id].to = v;
E[id].next = head[u];
head[u] = id++;
}
void dfs1(int u, int fa)
{
f[u][0] = c[u];
d[u] = max(d[u], d[fa] + 1);
for(int i = head[u]; i != -1; i = E[i].next)
{
int p = E[i].to;
if(p == fa) continue;
dfs1(p, u);
for(int j = 1; j <= m; j++) f[u][j] += f[p][j - 1];
}
}
void dfs2(int u, int fa)
{
for(int i = head[u];i != -1; i = E[i].next)
{
int p = E[i].to;
if(p == fa) continue;
for(int j = m; j >= 2; j--)
{
f[p][j] -= f[p][j - 2];
}
for(int j = 1; j <= m; j++)
{
f[p][j] += f[u][j - 1];
}
dfs2(p, u);
}
}
int main()
{
memset(head, -1, sizeof(head));
cin >> n >> m;
for(int i = 1; i < n; i++)
{
int u, v;
scanf("%d%d", &u, &v);
addedge(u, v);
addedge(v, u);
}
for(int i = 1; i <= n; i++) scanf("%d", &c[i]);
dfs1(1, -1);
dfs2(1, -1);
for(int i = 1; i<= n; i++)
{
int ans = 0;
for(int j = 0; j <= m; j++)
{
ans += f[i][j];
}
printf("%d\n", ans);
}
return 0;
}
[P2585 ZJOI2006]三色二叉树 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
f [ u ] [ 0 / 1 ] f[u][0/1] f[u][0/1]表示结点 u u u染成绿色或不染成绿色的最大值
f [ u ] [ 0 ] = f [ l e f t [ u ] ] [ 1 ] + f [ r i g h t [ u ] [ 1 ] ] f[u][0]=f[left[u]][1]+f[right[u][1]] f[u][0]=f[left[u]][1]+f[right[u][1]]
f [ u ] [ 1 ] = m a x ( f [ l e f t [ u ] ] [ 0 ] + f [ r i g h t [ u ] ] [ 1 ] , f [ l e f t [ u ] ] [ 1 ] + f [ r i g h t [ u ] ] [ 0 ] ) f[u][1]=max(f[left[u]][0]+f[right[u]][1],f[left[u]][1]+f[right[u]][0]) f[u][1]=max(f[left[u]][0]+f[right[u]][1],f[left[u]][1]+f[right[u]][0])
对于第i个节点不染绿色的情况,不存在两个节点都不染绿色的情况
因为然而根据抽屉原理,三个点染上两种颜色,一定有两个点的颜色相同。
不符合题意
#include <iostream>
#include <cstring>
#include <cstdio>
using namespace std;
const int N = 1000000;
int n, ch[N][2], f[N][3], g[N][3], tot = 0;
string a;
int build()
{
int now = ++tot; //新建节点编号
if(a[now - 1] == '2') ch[now][0] = build(), ch[now][1] = build();
else if(a[now - 1] == '1') ch[now][0] = build();
return now; //返回节点编号
}
void dfs(int x)
{
int l = ch[x][0], r = ch[x][1];
if(l) dfs(l); if(r) dfs(r); //递归两个儿子
if(!l && !r) //叶子节点初始化
f[x][0] = g[x][0] = 1, f[x][1] = f[x][2] = g[x][1] = g[x][2] = 0;
//转移方程
f[x][0] = max(f[l][1] + f[r][2], f[l][2] + f[r][1]) + 1;
f[x][1] = max(f[l][0] + f[r][2], f[l][2] + f[r][0]);
f[x][2] = max(f[l][0] + f[r][1], f[l][1] + f[r][0]);
g[x][0] = min(g[l][1] + g[r][2], g[l][2] + g[r][1]) + 1;
g[x][1] = min(g[l][0] + g[r][2], g[l][2] + g[r][0]);
g[x][2] = min(g[l][0] + g[r][1], g[l][1] + g[r][0]);
}
int main()
{
cin >> a; n = a.size(); //树的节点数等于字符串长度
memset(ch, 0, sizeof(ch)); //若一个节点没有该儿子,这个位置的编号为0
f[0][0] = f[0][1] = f[0][2] = 0; //初始化编号为0的节点
dfs(build());
printf("%d", max(f[1][0], max(f[1][1], f[1][2]))); //三种染色取最大值
printf(" %d", min(g[1][0], min(g[1][1], g[1][2]))); //三种染色取最小值
return 0;
}
[P2458 SDOI2006]保安站岗 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
没写
模板题
求树上最小点覆盖问题
f [ u ] [ 0 / 1 / 2 ] f[u][0/1/2] f[u][0/1/2]表示自身被覆盖,子节点被覆盖,父节点被覆盖
UVA1218 完美的服务 Perfect Service - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)(状态机)
没写
题意:一个网络中有 N 个节点,由 N-1 条边连通,每个节点是服务器或者客户端。如果节点 u 是客户端,就意味着 u 所连接的所有点中有且仅有一台服务器。求最少要多少台服务器才能满足要求。
d p [ u ] [ 0 ] dp[u][0] dp[u][0]表示 u u u是服务器,其儿子是不是服务器无所谓
d p [ u ] [ 1 ] dp[u][1] dp[u][1]表示 u u u不是服务器,它父节点是服务器,它的子节点不能是服务器
d p [ u ] [ 2 ] dp[u][2] dp[u][2]表示 u u u及其父节点不是服务器,其子节点必须有一个
d p [ u ] [ 0 ] = s u m ( m i n ( d p [ p ] [ 0 ] , d p [ p ] [ 1 ] ) + + 1 dp[u][0] = sum(min(dp[p][0], dp[p][1])+ + 1 dp[u][0]=sum(min(dp[p][0],dp[p][1])++1
d p [ u ] [ 1 ] = s u m ( d p [ p ] [ 2 ] ) dp[u][1]=sum(dp[p][2]) dp[u][1]=sum(dp[p][2])
d p [ u ] [ 2 ] = s u m ( d p [ p ] [ 2 ] ) + d p [ x ] [ 0 ] − d p [ x ] [ 2 ] dp[u][2]=sum(dp[p][2])+dp[x][0]-dp[x][2] dp[u][2]=sum(dp[p][2])+dp[x][0]−dp[x][2]
P1272 重建道路 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)(树形背包)
d p [ k ] [ i ] [ j ] dp[k][i][j] dp[k][i][j]表示以 i i i为根的子树,在前 k k k个儿子中,分离一个大小为 j j j的子树的最少的操作次数。
d p [ k + 1 ] [ i ] [ j ] = m i n ( d p [ k ] [ i ] [ j − t ] + d p [ f u l l s o n [ v ] ] [ v ] [ t ] ) dp[k+1][i][j]=min(dp[k][i][j-t]+dp[full_son[v]][v][t]) dp[k+1][i][j]=min(dp[k][i][j−t]+dp[fullson[v]][v][t])
$j=m->1 $
t = 1 − > j t=1->j t=1−>j
$ dp[i][j]=min(dp[i][j-t]+dp[v][t]-2)$
初始化:
d p [ u ] [ 0 ] = 0 dp[u][0]=0 dp[u][0]=0
d p [ u ] [ 1 ] = d u [ u ] dp[u][1]=du[u] dp[u][1]=du[u] 与 u u u相连的边数
减2的原因:我们通过v更新u,就要保留u->v 此条边,但是
想想我们的初始化,我们在 f [ u ] [ k ] f[u][k] f[u][k]中砍掉了这条边一次
又在 f [ v ] [ j − k ] f[v][j-k] f[v][j−k]中砍掉了这条边一次,所以要加回来
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int INF=0x3f3f3f3f;
const int N=201;
struct Edge{
int to,next;
}e[N*2];
int du[N],a[N],dp[N][N];
int n,k,res=INF,EdgeCnt=0;
void addedge(int u,int v){
int p=++EdgeCnt;
e[p].to=v;e[p].next=a[u];
a[u]=p;
}
void dfs(int u,int fa){
dp[u][1]=du[u];
for (int p=a[u];p;p=e[p].next){
int v=e[p].to;
if (v!=fa){
dfs(v,u);
for (int j=k;j>=1;j--)
for (int k=1;k<=j;k++)
dp[u][j]=min(dp[u][j],dp[u][j-k]+dp[v][k]-2);
}
}
res=min(res,dp[u][k]);
}
int main(){
scanf("%d%d",&n,&k);
memset(dp,0x3f,sizeof(dp));
for (int i=1;i<n;i++){
int u,v;
scanf("%d%d",&u,&v);
addedge(u,v);
addedge(v,u);
du[u]++;du[v]++;
}
dfs(1,0);
printf("%d",res);
return 0;
}
[P3177 HAOI2015]树上染色 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
题意:一棵树上一共 n n n个点, k k k个点是黑色的, n − k n-k n−k个点是白色的,问黑色点两两之间与白色点两两之间距离最大值之和。
考虑每一条边的贡献,如果两个同色的点在边的两侧,则贡献为 w w w,否则贡献为0。
用 s z [ ] sz[] sz[]维护子树中结点的数量
贡献为 t o t ∗ w tot*w tot∗w
t o t = k ∗ ( m − k ) + ( s z [ p ] − k ) ∗ ( n − m − ( s z [ p ] − k ) ) tot=k*(m-k)+(sz[p]-k)*(n-m-(sz[p]-k)) tot=k∗(m−k)+(sz[p]−k)∗(n−m−(sz[p]−k))
d p [ u ] [ k ] = m a x ( d p [ u ] [ k ] , d [ u ] [ j − k ] + d p [ p ] [ k ] + t o t ∗ w ) dp[u][k] = max(dp[u][k], d[u][j - k] + dp[p][k] + tot * w) dp[u][k]=max(dp[u][k],d[u][j−k]+dp[p][k]+tot∗w)
坑点好多!!!
j = m i n ( s z [ u ] , m ) − > 0 j = min(sz[u], m) ->0 j=min(sz[u],m)−>0
k = m i n ( j , s z [ p ] ) − > 1 k=min(j,sz[p])->1 k=min(j,sz[p])−>1
保证 u u u的子节点 p p p一定被选上
每次更新的时候 d p [ u ] [ k ] dp[u][k] dp[u][k]一定需要 d p [ u ] [ j − k ] dp[u][j-k] dp[u][j−k]被更新才能被更新
总结:树形背包一般都需要维护 s z [ ] sz[] sz[]
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
typedef long long ll;
const int MAXN = 2020;
int n, m, id;
int head[MAXN], sz[MAXN];
ll f[MAXN][MAXN];
struct Edge{
int to, next, w;
}E[MAXN * 2];
void addedge(int u, int v, int w)
{
E[id].w = w;
E[id].to = v;
E[id].next = head[u];
head[u] = id++;
}
void dfs(int u, int fa)
{
sz[u] = 1;
f[u][0] = f[u][1] = 0;
for(int i = head[u]; i != -1; i = E[i].next)
{
int p = E[i].to, w = E[i].w;
if(p == fa) continue;
dfs(p, u);
sz[u] += sz[p];
for(int j = min(m, sz[u]); j >= 0; j--)
{
for(int k = 0; k <= min(sz[p], j); k++)
{
if(f[u][j - k] == -1) continue;
ll tot = (ll)k * (m - k) + (ll)(sz[p] - k) * (n - m - (sz[p] - k));
f[u][j] = max(f[u][j], f[u][j - k] + f[p][k] + (ll)tot * w);
}
}
}
}
int main()
{
memset(head, -1, sizeof(head));
cin >> n >> m;
if(n - m < m) m = n - m;
for(int i = 1; i < n; i++)
{
int u, v, w;
scanf("%d%d%d", &u, &v, &w);
addedge(u, v, w);
addedge(v, u, w);
}
memset(f, -1, sizeof(f));
dfs(1, -1);
printf("%lld\n", f[1][m]);
return 0;
}
[P3174 HAOI2009]毛毛虫 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
题意:求一条链与其相连的边所覆盖的点的最大值
从根节点开始维护最长链与次长链,注意每一种情况的特判,注意分类讨论的严谨
实时更新 a n s ans ans
s z [ u ] sz[u] sz[u]维护以 u u u为根的最长毛毛虫数量
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXN = 3e5 + 60;
int head[MAXN], sz[MAXN], d[MAXN];
int id, n, m, ans;
struct Edge{
int to, next;
}E[MAXN * 2];
void addedge(int u, int v)
{
E[id].to = v;
E[id].next = head[u];
head[u] = id++;
return;
}
void dfs(int u, int fa)
{
sz[u] = 0;
int mx0 = 0, mx1 = 0;
for(int i = head[u]; i != -1; i = E[i].next)
{
int p = E[i].to;
if(p == fa) continue;
dfs(p, u);
sz[u] = max(sz[u], sz[p]);
if(sz[p] >= mx0) {mx1 = mx0; mx0 = sz[p];}
else if(sz[p] > mx1) mx1 = sz[p];
}
if(mx0 == 0) ans = max(ans, d[u] + 1);
else if(mx1 == 0) ans = max(ans, mx0 + d[u]);
else ans = max(ans, mx0 + mx1 + d[u] - 1);
sz[u] += ((d[u] == 1) ? 1 : (d[u] - 1));
}
int main()
{
memset(head, -1, sizeof(head));
scanf("%d%d", &n, &m);
for(int i = 1; i <= m; i++)
{
int u, v;
scanf("%d%d", &u, &v);
addedge(u, v);
addedge(v, u);
d[u] ++;
d[v] ++;
}
dfs(1, -1);
printf("%d\n", ans);
return 0;
}
P6554 Promises I Can’t Keep - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)(换根DP)
第一遍dfs时的根为root, f u f_u fu表示路径和, w u w_u wu表示该点的权值, c n t u cnt_u cntu表示以 u u u为根的子树中叶子节点的数量.
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int N = 1e6 + 5;
int head[N], ver[N], net[N], idx;
int cnt[N], dg[N], root;
bool is[N];
double sum[N], f[N], w[N];
void add(int a, int b)
{
net[idx] = head[a];
ver[idx] = b;
head[a] = idx++;
}
void dfs1(int u, int fa)
{
for (int i = head[u]; ~i; i = net[i])
{
int v = ver[i];
if (v == fa)
continue;
dfs1(v, u);
cnt[u] += cnt[v];
sum[u] += sum[v] + w[u] * cnt[v];
}
if (!cnt[u])
sum[u] = w[u], cnt[u] = 1, is[u] = true;
}
void dfs2(int u, int fa)
{
for (int i = head[u]; ~i; i = net[i])
{
int v = ver[i];
if (v == fa)
continue;
if (is[v])
f[v] = f[u] - w[u] + (cnt[root] - 2) * w[v];
else
f[v] = f[u] - w[u] * cnt[v] + (cnt[root] - cnt[v]) * w[v];
dfs2(v, u);
}
}
int main()
{
memset(head, -1, sizeof(head));
int n;
scanf("%d", &n);
for (int i = 1; i < n; i++)
{
int u, v;
scanf("%d%d", &u, &v);
add(u, v), add(v, u);
dg[u]++, dg[v]++;
}
for (int i = 1; i <= n; i++)
{
scanf("%lf", &w[i]);
if (dg[i] > 1)
root = i;//找出根节点
}
dfs1(root, 0);
f[root] = sum[root];
dfs2(root, 0);
double ans = -1e13;
for (int i = 1; i <= n; i++)
ans = max(ans, (double)f[i] / (cnt[root] - is[i]));//计算期望
printf("%.4lf", ans);
return 0;
}