树形DP
1.统计人数
1.题目
一家公司里有 n n n 个员工,他们的编号分别是 1 1 1 到 n n n,其中 1 1 1 号员工是公司 CEO,CEO 在公司里没有上司。除了 CEO 外,每个人都有一个直接上司。我们想知道,每个人的团队(包括他/她自己、他/她的直接下属和间接下属)一共有多少人?
输入格式
第一行一个整数 n。
接下来一行 n−1个整数 f2,f3,…,fn,其中 fi (1≤fi<i) 表示 i号员工的上司的编号。
输出格式
输出一行 n个整数,分别表示 1∼n 号员工的团队人数。
2.分析
i i i 号员工的团队人数等于他/她的每个直接下级的团队人数的总和 + 1 +1 +1。
有 d f s dfs dfs 和 b f s bfs bfs 两种选法。
3.代码
dfs写法
#include <bits/stdc++.h>
#define ll long long
using namespace std;
struct node{
node *next;
int where;
} *first[100001], a[100001];
int n, l, f[100001];
inline void makelist(int x,int y){//连一条从x到y的边
a[++l].where = y;
a[l].next = first[x];
first[x] = &a[l];
}
inline void solve(int i){
f[i] = 1;//一开始就自己一个人
for (node *x = first[i]; x; x = x->next)//找所有的儿子
{
solve(x->where);
f[i] += f[x->where];//加上儿子子树的个数
}
}
int main()
{
scanf("%d", &n);
memset(first, 0, sizeof(first));
l = 0;
for (int i = 2; i <= n;i++){
int x;
scanf("%d", &x);
makelist(x, i);
}
solve(1);//解决以1号点为子树的情况
for (int i = 1; i <= n;i++)
printf("%d ", f[i]);
}
bfs
#include <bits/stdc++.h>
#define ll long long
using namespace std;
struct node
{
node *next;
int where;
} *first[100001], a[100001];
int n, l, f[100001], c[100001];
inline void makelist(int x, int y)
{ // 连一条从x到y的边
a[++l].where = y;
a[l].next = first[x];
first[x] = &a[l];
}
int main()
{
scanf("%d", &n);
memset(first, 0, sizeof(first));
l = 0;
for (int i = 2; i <= n; i++)
{
int x;
scanf("%d", &x);
makelist(x, i);
}
c[1] = 1;//队列,找出bfs序
for (int k = 1, l = 1; l <= k; l++)
{
int m = c[l];//头元素
for (node *x = first[m]; x; x = x->next)//下属点
c[++k] = x->where;
}
for (int i = n; i; i--)//倒着算的,算到前面的时候后面都计算过了
{
int m = c[i];
f[m] = 1;
for (node *x = first[m]; x; x = x->next)
{
f[m] += f[x->where];
}
}
for (int i = 1; i <= n; i++)
printf("%d ", f[i]);
}
2.没有上司的舞会
1.题目
一家公司里有 n n n 个员工,他们的编号分别是 1 1 1 到 n n n,其中 1 1 1 号员工是公司 CEO,CEO 在公司里没有上司。除了 CEO 外,每个人都有一个直接上司。今天公司要办一个舞会,为了大家玩得尽兴,如果某个员工的直接上司来了,他/她就不想来了。 i i i 号员工来参加舞会会为大家带来 a i a_i ai 点快乐值。现在我们想要确定一组员工参加舞会的方案,使得快乐值总和最大。请求出快乐值总和最大是多少。
输入格式
第一行一个整数 n。
接下来一行 n−1个整数 f2,f3,…,fn,其中 fi (1≤fi<i) 表示 i号员工的上司的编号。
接下来一行 n个整数 a1,a2,…,an
表示每个员工参加舞会带来的快乐值。
输出格式
一行一个整数表示答案。
2.分析
考虑 i i i 号员工的团队,有参加和不参加两种情况,分别求最大值。
如果他参加,他的直接下属都不参加;如果他不参加,考虑他直接下属参加和不参加。
f [ i ] [ 0 ] f[i][0] f[i][0] 和 f [ i ] [ 1 ] f[i][1] f[i][1] 分别表示考虑 i i i 号员工的团队,他不参加/参加的最大快乐值。
3.代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;
struct node
{
node *next;
int where;
} *first[100001], a[100001];
int n, l ,c[100001],v[100001];
ll f[100001][2];
inline void makelist(int x, int y)
{ // 连一条从x到y的边
a[++l].where = y;
a[l].next = first[x];
first[x] = &a[l];
}
inline void solve(int i){
f[i][1] = v[i];
for (node *x = first[i]; x;x=x->next){
solve(x->where);
f[i][0] += max(f[x->where][0], f[x->where][1]);
f[i][1] += f[x->where][0];
}
}
int main()
{
scanf("%d", &n);
memset(first, 0, sizeof(first));
l = 0;
for (int i = 2; i <= n;i++){
int x;
scanf("%d", &x);
makelist(x, i);
}
for (int i = 1; i <= n;i++)
scanf("%d", &v[i]);
solve(1);
printf("%lld\n", max(f[1][0], f[1][1]));
}
3.新的背包
1.题目
有 n n n 种物品要放到一个袋子里,袋子的总容量为 m m m,每种物品都有 m m m 个,单个物品的体积都是 1 1 1。对于第 i i i 种物品,如果我们一共取了 j ( j ≥ 1 ) j (j≥1) j(j≥1) 个,会获得 w i , j w_{i,j} wi,j 的收益。请问如何选择物品,使得在物品的总体积不超过 m m m 的情况下,获得的总收益最大?请求出最大总收益。
输入格式
第一行两个整数 n,m。
接下来 n行,每行 m 个整数 wi,1,…,wi,m。
输出格式
一行一个整数表示答案。
2.分析
f [ i ] [ j ] f[i][j] f[i][j] 表示前 i i i 种物品,总体积不超过 j j j。然后枚举第 i i i 种物品取了 k k k 个。
3.代码
//二维
// #include <bits/stdc++.h>
// #define ll long long
// using namespace std;
// int n, m, f[501][501], w[501][501];
// int main()
// {
// scanf("%d%d", &n, &m);
// for (int i = 1; i <= n;i++)
// for (int j = 1; j <= m;j++)
// scanf("%d", &w[i][j]);
// for (int i = 1; i <= n;i++)
// for (int j = 0; j <= m;j++)
// for (int k = 0; k <= j;k++)
// f[i][j] = max(f[i][j], f[i - 1][j - k] + w[i][k]);
// printf("%d\n", f[n][m]);
// }
//一维
#include <bits/stdc++.h>
#define ll long long
using namespace std;
int n, m, f[501], w[501][501];
int main()
{
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
scanf("%d", &w[i][j]);
for (int i = 1; i <= n; i++)
for (int j = m; j >= 0; j--)
for (int k = 0; k <= j; k++)
f[j] = max(f[j], f[j - k] + w[i][k]);
printf("%d\n", f[m]);
}
4.没有上司的舞会2(书上背包)
1.题目
一家公司里有 n n n 个员工,他们的编号分别是 1 1 1 到 n n n,其中 1 1 1 号员工是公司 CEO,CEO 在公司里没有上司。除了 CEO 外,每个人都有一个直接上司。今天公司要办一个舞会,为了大家玩得尽兴,如果某个员工的直接上司来了,他/她就不想来了。 i i i 号员工来参加舞会会为大家带来 a i a_i ai 点快乐值。由于场地有大小限制,场地最多只能容纳 m m m 个人。现在我们想要确定一组员工参加舞会的方案,使得快乐值总和最大。请求出快乐值总和最大是多少。
输入格式
第一行两个整数 n,m。
接下来一行 n−1个整数 f2,f3,…,fn,其中 fi (1≤fi<i) 表示 i号员工的上司的编号。
接下来一行 n个整数 a1,a2,…,an
表示每个员工参加舞会带来的快乐值。
输出格式
一行一个整数表示答案。
2.分析
有人数限制,考虑 i i i 号员工的团队,除了考虑参加与否,还要考虑人数。
用 f [ i ] [ j ] [ 0 ] , f [ i ] [ j ] [ 1 ] f[i][j][0],f[i][j][1] f[i][j][0],f[i][j][1] 分别考虑 i i i 号员工的团队,团队中一共有不超过 j j j 个人参加,他不参加和参加的最大快乐值。
在 i i i 确定的时候,就转换成了求上一题。
3.代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;
struct node
{
node *next;
int where;
} *first[100001], a[100001];
int n, m, l, c[100001], v[100001];
ll f[501][501][2];
inline void makelist(int x, int y)
{ // 连一条从x到y的边
a[++l].where = y;
a[l].next = first[x];
first[x] = &a[l];
}
inline void solve(int i)
{
for (node *x = first[i]; x; x = x->next)
{
solve(x->where);//先遍历所有的子树
for (int j = m; j >= 0; j--)
for (int k = 0; k <= j; k++)//枚举来了多少人
{
f[i][j][0] = max(f[i][j][0], f[i][j - k][0] + max(f[x->where][k][0], f[x->where][k][1]));
f[i][j][1] = max(f[i][j][1], f[i][j - k][1] + f[x->where][k][0]);
}
}
for (int j = m; j; --j)
f[i][j][1] = f[i][j - 1][1] + v[i];//因为之前都是只考虑员工,所以在这里加上,子树来了j-1个人,然后加上现在这个
f[i][0][1] = 0;
}
int main()
{
scanf("%d%d", &n, &m);
memset(first, 0, sizeof(first));
l = 0;
for (int i = 2; i <= n; i++)
{
int x;
scanf("%d", &x);
makelist(x, i);
}
for (int i = 1; i <= n; i++)
scanf("%d", &v[i]);
solve(1);
printf("%lld\n", max(f[1][m][0], f[1][m][1]));
}
5.树的最长路径
1.题目
给定一棵树,树中包含 n n n 个结点(编号 1 n 1~n 1 n)和 n − 1 n−1 n−1 条无向边,每条边都有一个权值。
现在请你找到树中的一条最长路径。
换句话说,要找到一条路径,使得使得路径两端的点的距离最远。
注意:路径中可以只包含一个点。
输入格式:
第一行包含整数 n 。接下来 n − 1行,每行包含三个整数 $ a_i,b_i,c_i $,表示点 $ a_i
和
和
和 b_i$ 之间存在一条权值为
c
i
c_i
ci的边。
输出格式:
输出一个整数,表示树的最长路径的长度。
2.分析
1.任取一点作为起点,找到距离最远的一个点
u
u
u。
2.再找距离
u
u
u 最远的一点
v
v
v。能
B
F
S
BFS
BFS就
B
F
S
BFS
BFS。
那么 u u u 和 v v v 之间的路径就是一条直径。
我们对每个路径进行分类,每次找到高度最高的点,然后将通过它的路径都归到这个点上。
到时候枚举所有的点即可,求出每个点挂的路径的最长的即可。
如何去求挂在它上的最大值,按它儿子方向找,找到从它往下走的最长边和次长边。
3.代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 10010, M = N * 2;
int n;
int h[N], e[M], w[M], ne[M], idx;
int ans;
void add(int a, int b, int c)
{
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
}
int dfs(int u, int father) // father保证每次都是往下找
{
int dist = 0; // 表示从当前带你往下走的最大长度
int d1 = 0, d2 = 0; // 存最大值和次大值
for (int i = h[u]; i != -1; i = ne[i])
{
int j = e[i];
if (j == father)
continue;
int d = dfs(j, u) + w[i];
dist = max(dist, d);
if (d >= d1)
d2 = d1, d1 = d;
else if (d > d2)
d2 = d;
}
ans = max(ans, d1 + d2);
return dist;
}
int main()
{
cin >> n;
memset(h, -1, sizeof h);
for (int i = 0; i < n - 1; i++)
{
int a, b, c;
cin >> a >> b >> c;
add(a, b, c);
add(b, a, c);
}
dfs(1, -1);
cout << ans << endl;
}
6.树的中心
1.题目
给定一棵树,树中包含 n n n 个结点(编号 1 n 1~n 1 n)和 n − 1 n−1 n−1 条无向边,每条边都有一个权值。
请你在树中找到一个点,使得该点到树中其他结点的最远距离最近。
输入格式:
第一行包含整数 n 。接下来 n−1行,每行包含三个整数 $ a_i,b_i,c_i$,表示点 $ a_i$和 $ b_i$ 之间存在一条权值为 $ c_i$的边。
输出格式:
输出一个整数,表示所求点到树中其他结点的最远距离。
2.分析
对于每个节点,都有往下走和往上走。
往上走,先走到父节点,可以分为从父节点往上走,从父节点往下走不经过这个点,从父节点往下走经过这个点,如果第三种情况最长的话,我们就要记录一个次长值用来判断,因为第三种肯定不能要。
往下走,就直接走,找最长的。
3.代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 10010, M = N * 2, INF = 0x3f3f3f3f;
int n;
int h[N], e[M], w[M], ne[M], idx;
int d1[N], d2[N], up[N],p1[N],p2[N]; // d1表示往下走最长,d2是次长,up是往上最长
int dfs_d(int u,int father)
{
d1[u] = d2[u] = -INF;
for (int i = h[u]; i != -1;i=ne[i])
{
int j = e[i];
if(j==father)
continue;
int d = dfs_d(j, u) + w[i];
if(d>=d1[u])
{
d2[u] = d1[u], d1[u] = d;
p2[u] = p1[u], p1[u] = j;
}
else if(d>d2[u])
d2[u] = d, p2[u] = j;
}
if(d1[u]==-INF)
d1[u] = d2[u] = 0;
return d1[u];
}
void dfs_u(int u,int father)
{
for (int i = h[u]; i != -1;i=ne[i])
{
int j = e[i];
if(j==father)
continue;
if(p1[u]==j)
up[j] = max(up[u] , d2[u]) + w[i];
else
up[j] = max(up[u], d1[u]) + w[i];
dfs_u(j, u);
}
}
void add(int a,int b,int c)
{
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
}
int main()
{
cin >> n;
memset(h, -1, sizeof h);
for (int i = 0; i < n - 1;i++)
{
int a, b, c;
cin >> a >> b >> c;
add(a, b, c), add(b, a, c);
}
dfs_d(1, -1);
dfs_u(1, -1);
int res = INF;
for (int i = 1; i <= n;i++)
res = min(res, max(d1[i], up[i]));
}
7.数字转换
1.题目
如果一个数 x x x 的约数之和 y y y(不包括他本身)比他本身小,那么 x x x 可以变成 y y y, y y y 也可以变成 x x x。
例如, 4 4 4 可以变为 3 3 3, 1 1 1 可以变为 7 7 7。
限定所有数字变换在不超过 n n n 的正整数范围内进行,求不断进行数字变换且不出现重复数字的最多变换步数。
输入格式:
输入一个正整数 n。
输出格式:
输出不断进行数字变换且不出现重复数字的最多变换步数。
2.分析
以约数和作为父节点,能构成一堆树,相当于在这些树中找长度最长的路径。
快速求约数,可以用筛法,去找这个约数的倍数。
3.代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 50010;
int n;
int h[N], e[N], ne[N], idx;
int sum[N];
bool st[N];
int ans;
void add(int a, int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
int dfs(int u)
{
int d1 = 0, d2 = 0;
for (int i = h[u]; i != -1; i = ne[i])
{
int j = e[i];
int d = dfs(j) + 1;
if (d >= d1)
d2 = d1, d1 = d;
else if (d > d2)
d2 = d;
}
ans = max(ans, d1 + d2);
return d1;
}
int main()
{
cin >> n;
for (int i = 1; i <= n; i++)
for (int j = 2; j <= n / i; j++) // 怕溢出
sum[i * j] += i;
memset(h, -1, sizeof h);
for (int i = 2; i <= n; i++)
{
if (i > sum[i])
{
add(sum[i], i);
st[i] = true;
}
}
for (int i = 1; i <= n; i++)
if (!st[i])
dfs(i);
cout << ans << endl;
}
8.二叉苹果树
1.题目
一棵二叉苹果树,如果数字有分叉,一定是分两叉,即没有只有一个儿子的节点。这棵树共 N N N 个节点,标号 1 1 1 至 N N N,树根编号一定为 1 1 1。
我们用一根树枝两端连接的节点编号描述一根树枝的位置。一棵苹果树因为树枝太多了,需要剪枝。但是一些树枝上长有苹果,给定需要保留的树枝数量,求最多能留住多少苹果。
这里的保留是指最终与 1 1 1 号点连通。
输入格式:
第一行包含两个整数 N和 Q,分别表示树的节点数以及要保留的树枝数量。接下来 N−1行描述树枝信息,每行三个整数,前两个是它连接的节点的编号,第三个数是这根树枝上苹果数量。
输出格式:
输出仅一行,表示最多能留住的苹果的数量。
2.分析
f ( i , j ) f(i,j) f(i,j) 表示以 i i i 为根的子树中选 j j j个树枝的最大价值。类似有依赖的背包问题。
3.代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 110, M = N * 2;
int n, m;
int h[N], e[M], w[M], ne[M], idx;
int f[N][N];
void add(int a,int b,int c)
{
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
}
void dfs(int u,int father)
{
for (int i = h[u]; ~i;i=ne[i])
{
int j = e[i];
if(j==father)
continue;
dfs(e[i],u);
for (int j = m; j >= 0;j--)
{
for (int k = 0; k < j;k++)
f[u][j] = max(f[u][j], f[u][j - k - 1] + f[e[i]][k] + w[i]);
}
}
}
int main()
{
cin >> n >> m;
memset(h, -1, sizeof(h));
for (int i = 0; i < n-1;i++)
{
int a, b, c;
cin >> a >> b >> c;
add(a, b, c);
add(b, a, c);
}
dfs(1,-1);
cout << f[1][m] << endl;
}
9.战略游戏
1.题目
Bob喜欢玩电脑游戏,特别是战略游戏。但是他经常无法找到快速玩过游戏的办法。现在他有个问题。他要建立一个古城堡,城堡中的路形成一棵树。他要在这棵树的结点上放置最少数目的士兵,使得这些士兵能了望到所有的路。注意,某个士兵在一个结点上时,与该结点相连的所有边将都可以被了望到。
请你编一程序,给定一树,帮Bob计算出他需要放置最少的士兵。
输入格式
输入包含多组测试数据,每组测试数据用以描述一棵树。
对于每组测试数据,第一行包含整数N,表示树的节点数目。
接下来N行,每行按如下方法描述一个节点。
节点编号:(子节点数目) 子节点 子节点 …
节点编号从0到N-1,每个节点的子节点数量均不超过10,每个边在输入数据中只出现一次。
输出格式
对于每组测试数据,输出一个占据一行的结果,表示最少需要的士兵数。
2.分析
每条边上最少选择一个点,求最小的权值。跟上司的误会对称。
f [ i ] [ j ] f[i][j] f[i][j] 表示所有以 i i i 为根的子树中选,且点 i i i 的状态是 j j j 的所有选法。
3.代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 1510;
int n;
int h[N], e[N], ne[N], idx;
int f[N][2];
bool st[N];
void add(int a,int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
void dfs(int u)
{
f[u][0] = 0;
f[u][1] = 1;
for (int i = h[u]; ~i;i=ne[i])
{
int j = e[i];
dfs(j);
f[u][0] += f[j][1];
f[u][1] += min(f[j][0], f[j][1]);
}
}
int main()
{
while(scanf("%d",&n)==1)
{
memset(h, -1, sizeof h);
idx = 0;
memset(st, 0, sizeof st);
for (int i = 0; i < n;i++)
{
int id, cnt;
scanf("%d:(%d)", &id, &cnt);
while(cnt--)
{
int ver;
scanf("%d", &ver);
add(id, ver);
st[ver] = true;
}
}
int root = 0;
while(st[root])
root++;
dfs(root);
printf("%d\n",min(f[root][0], f[root][1]));
}
}
10.皇宫看守
1.题目
太平王世子事件后,陆小凤成了皇上特聘的御前一品侍卫。
皇宫各个宫殿的分布,呈一棵树的形状,宫殿可视为树中结点,两个宫殿之间如果存在道路直接相连,则该道路视为树中的一条边。
已知,在一个宫殿镇守的守卫不仅能够观察到本宫殿的状况,还能观察到与该宫殿直接存在道路相连的其他宫殿的状况。
大内保卫森严,三步一岗,五步一哨,每个宫殿都要有人全天候看守,在不同的宫殿安排看守所需的费用不同。
可是陆小凤手上的经费不足,无论如何也没法在每个宫殿都安置留守侍卫。
帮助陆小凤布置侍卫,在看守全部宫殿的前提下,使得花费的经费最少。
输入格式
输入中数据描述一棵树,描述如下:
第一行 n,表示树中结点的数目。
第二行至第 n+1 行,每行描述每个宫殿结点信息,依次为:该宫殿结点标号 i,在该宫殿安置侍卫所需的经费 k,该结点的子结点数 m,接下来 m 个数,分别是这个结点的 m 个子结点的标号 r1,r2,…,rm。
对于一个 n 个结点的树,结点标号在 1 到 n 之间,且标号不重复。
输出格式
输出一个整数,表示最少的经费。
2.分析
这道题是每个点以及周围的临点至少有一个点。
f ( i , 0 ) f(i,0) f(i,0) 表示点 i i i 被父节点看到的最小花费, f ( i , 1 ) f(i,1) f(i,1) 表示点 i i i 被子节点看到, f ( i , 2 ) f(i,2) f(i,2) 表示点 i i i 自己放一个。
3.代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 1510;
int n;
int h[N], e[N], w[N], ne[N], idx;
int f[N][3];
bool st[N];
void add(int a, int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
void dfs(int u)
{
f[u][2] = w[u];
for (int i = h[u]; ~i; i = ne[i])
{
int j = e[i];
dfs(j);
f[u][0] += min(f[j][1], f[j][2]);
f[u][2] += min(min(f[j][0], f[j][1]), f[j][2]);
}
f[u][1] = 1e9;
for (int i = h[u]; ~i; i = ne[i])
{
int j = e[i];
f[u][1] = min(f[u][1], f[j][2] + f[u][0] - min(f[j][1], f[j][2]));
}
}
int main()
{
cin >> n;
memset(h, -1, sizeof h);
for (int i = 1; i <= n; i++)
{
int id, cost, cnt;
cin >> id >> cost >> cnt;
w[id] = cost;
while (cnt--)
{
int ver;
cin >> ver;
add(id, ver);
st[ver] = true;
}
}
int root = 1;
while (st[root])
root++;
dfs(root);
cout << min(f[root][1], f[root][2]) << endl;
}
11.距离和
1.题目
有一棵 n 个节点的树,节点编号从 1 到 n。请求出每个节点到其他所有节点的距离的和。
定义两个节点的距离为它们之间的简单路径上经过了多少条边。
输入格式
第一行一个整数 n表示节点数。
接下来 n−1行,每行两个整数 x,y 表示 x 号节点和 y号节点之间有一条边。
数据保证读入的是一棵树。
输出格式
输出共 n行,第 i 行一个整数表示 i 号节点到其他所有节点的距离的和。
2.分析
先考虑以1为根的有根树的情况,用size【i】表示以i为根的子树中有多少个点,f【i】表示考虑以i为根的子树,i到子树中其他所有点的距离的和。
假设j是i的儿子,以j为根的子树对f【i】的贡献为f【j】+size【j】
所以f【i】就是所有子树的f【j】的和+size【i】-1
我们用v【i】表示一个点的父亲作为它的子树时的贡献
当根从1变成它儿子i的时候
v【i】=f【1】-f【i】-size【i】+n-size【i】
当根从x变成它儿子y的时候
v【y】=v【x】+f【x】-f【y】-size【y】+n-size【y】
对于y,以它为根的答案为v【y】+f【y】
3.代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;
struct node
{
node *next;
int where;
} *first[100001], a[200001];
int n, l;
int sze[100001];
long long f[100001], v[100001];
bool b[100001];
inline void makelist(int x, int y)
{
a[++l].where = y;
a[l].next = first[x];
first[x] = &a[l];
}
inline void up(int i)//求f数组
{
sze[i] = 1;
b[i] = true;
for (node *x = first[i]; x; x = x->next)
if (!b[x->where])
{
up(x->where);
sze[i] += sze[x->where];
f[i] += f[x->where];
}
f[i] += sze[i] - 1;
}
inline void down(int i)//求z数组
{
b[i] = true;
for (node *x = first[i]; x; x = x->next)
{
if (!b[x->where])
{
v[x->where] = v[i] + f[i] - f[x->where] - sze[x->where] + n - sze[x->where];
down(x->where);
}
}
}
int main()
{
memset(first, 0, sizeof(first));
l = 0;
scanf("%d", &n);
for (int i = 1; i < n; i++)
{
int x, y;
scanf("%d%d", &x, &y);
makelist(x, y);
makelist(y, x);
}
memset(b, false, sizeof b);
up(1);
memset(b, false, sizeof b);
down(1);
for (int i = 1; i <= n; i++)
printf("%lld\n", f[i] + v[i]);
}
12.流
1.题目
有一棵 n 个节点的树,节点编号从 1 到 n,树上的每条边都有流量限制。令某一个节点为根节点,向这个节点灌水,最终从叶子节点流出的水量之和为这个节点的最大流量。请求出每个节点的最大流量。
输入格式
第一行一个整数 n
表示节点数。
接下来 n−1行,每行三个整数 x,y,z 表示 x 号节点和 y 号节点之间有一条流量限制为 z的边。数据保证读入的是一棵树。
输出格式
输出共 n行,第 i 行一个整数表示 i 号节点的最大流量。
2.分析
先考虑以1为根的有根树的情况,f[i] 表示考虑以i为根的子树,最多可以承接多少上面下来的流量。
假设j是i的儿子,以j为根的子树对f【i】的贡献为min(f[i],c[j])
当根从1变成它的儿子i的时候。我们用v[i]表示一个点的父亲作为它的子树时的贡献,v[1]=0
v[i]=min(f[1]-min(c[i],f[i]),c[i])
当根从x变成它的儿子y的时候,v[y]=min(v[x]+f[x]-min(c[y],f[y]),c[y])
3.代码
#include <bits/stdc++.h>
using namespace std;
struct node
{
node *next;
int where, v;
} *first[100001], a[200001];
int n, l;
long long f[100001], v[100001];
bool b[100001];
inline void makelist(int x, int y, int z)
{
a[++l].where = y;
a[l].v = z;
a[l].next = first[x];
first[x] = &a[l];
}
inline void up(int i)
{
b[i] = true;
bool ok = true;
for (node *x = first[i]; x; x = x->next)
if (!b[x->where])
{
up(x->where);
f[i] += min(1LL * x->v, f[x->where]);
ok = false;
}
if (ok)
f[i] = 1 << 30;
}
inline void down(int i)
{
b[i] = true;
for (node *x = first[i]; x; x = x->next)
if (!b[x->where])
{
if (v[i] + f[i] - min(1LL * x->v, f[x->where]))
v[x->where] = min(v[i] + f[i] - min(1LL * x->v, f[x->where]), 1LL * x->v);
else
v[x->where] = x->v;
down(x->where);
}
}
int main()
{
memset(first, 0, sizeof(first));
l = 0;
scanf("%d", &n);
for (int i = 1; i < n; i++)
{
int x, y, z;
scanf("%d%d%d", &x, &y, &z);
makelist(x, y, z);
makelist(y, x, z);
}
memset(b, false, sizeof(b));
up(1);
memset(b, false, sizeof(b));
down(1);
for (int i = 1; i <= n; i++)
if (f[i] != 1 << 30)
printf("%lld\n", f[i] + v[i]);
else
printf("%lld\n", v[i]);
}
13.最长路径
1.题目
有一棵 n 个节点的树,节点编号从 1 到 n。对于每个节点,请求出经过它的长度最长的简单路径有多长。
定义一条路径的长度为这条路径上经过了多少条边。
输入格式
第一行一个整数 n表示节点数。
接下来 n−1行,每行两个整数 x,y 表示 x 号节点和 y号节点之间有一条边。数据保证读入的是一棵树。
输出格式
输出共 n行,第 i 行一个整数表示经过 i 号节点的长度最长的简单路径有多长。
2.分析
先考虑以1为根的有根树的情况,我们用f[i]记录考虑以i为根的子树,从i的不同子树里连上来的到i的最长的两条路径。
假设j是i的儿子,以j为根的子树连上来的路径是连接到j的最长的路径加上i
当根从1变成它的儿子i的时候,用v[i]表示一个点的父亲作为它的子树时从里面练上来的到i的最长路径有多长
3.代码
#include <bits/stdc++.h>
using namespace std;
struct node {
node *next;
int where;
} *first[100001], a[200001];
int n, l, f[100001][2][2], v[100001];
bool b[100001];
inline void makelist(int x, int y) {
a[++l].where = y;
a[l].next = first[x];
first[x] = &a[l];
}
inline void up(int i) {
b[i] = true;
for (node *x = first[i]; x; x = x->next)
if (!b[x->where]) {
up(x->where);
if (f[x->where][0][0] + 1 > f[i][0][0])
f[i][1][0] = f[i][0][0], f[i][1][1] = f[i][0][1], f[i][0][0] = f[x->where][0][0] + 1, f[i][0][1] = x->where;
else
if (f[x->where][0][0] + 1 > f[i][1][0])
f[i][1][0] = f[x->where][0][0] + 1, f[i][1][1] = x->where;
}
}
inline void down(int i) {
b[i] = true;
for (node *x = first[i]; x; x = x->next)
if (!b[x->where]) {
if (f[i][0][1] == x->where)
if (v[i] > f[i][1][0])
v[x->where] = v[i] + 1;
else
v[x->where] = f[i][1][0] + 1;
else
v[x->where] = max(f[i][0][0], v[i]) + 1;
down(x->where);
}
}
int main() {
memset(first, 0, sizeof(first));
l = 0;
scanf("%d", &n);
for (int i = 1; i < n; i++) {
int x, y;
scanf("%d%d", &x, &y);
makelist(x, y);
makelist(y, x);
}
memset(b, false, sizeof(b));
up(1);
memset(b, false, sizeof(b));
down(1);
for (int i = 1; i <= n; i++)
printf("%d\n", f[i][0][0] + f[i][1][0] + v[i] - min(min(f[i][0][0], f[i][1][0]), v[i]));
}