题目
题目描述:
F(x)是一种属性。F(x)(节点x的累积度)定义如下:
- 树的每个边缘的权值是一个整数。
- 树中度为1的节点称为终端。
- 每条边的流量不能超过其权值。
- F(x)是节点x可以流到其他终端节点的最大流。
输入描述:
输入的第一行是整数T,它表示测试用例的数量。每个测试用例的第一行是一个正整数n。
接下来的n-1行中的每行包含三个由空格分隔的整数x,y,z,表示节点x和y之间存在边,并且边的容量为z。节点从1到n编号。
所有元素都是不超过200000的非负整数。您可以假定测试数据都是树度量。
输出描述:
对于每个测试用例,将结果单行输出。
解析
hanhan因为刚刚做完了树学这道题,所以对这个题目也有了一定的了解,推出了公式,但是还是有点不会写,orz
首先还是日常的操作步骤:
- 基本操作,用树的孩子表示法存储好树结构。
- 接下来我们就要求出每个节点做根时的最大流。
- 最后找最大的输出就行了。
那么最重要的就是这里了,我们该怎么求出每个节点的最大流呢?
正常操作,直接遍历,1000%就TLE了。所以我们就要想到求出递推式,用树形dp算法得到答案。
我们这里分两步进行操作:
- 递归+递推得出每个节点为根的最大流。(存入dp)
- 树形dp递推得出以每个节点为根节点产生的最大流。(存入Flow)
(ps:这两个说法很接近,但是完全不一样,见图解)
接下来就是求出递推公式了:
每个节点为根的最大流(dp数组)获得:
- 各节点为根的最大流的递推公式不难得出:dp[i]=∑min(dp[j],edge[i][j])( j 是 i 的子节点,dege[i][j]指 i 与 j 之间的距离)
- 但是在代码中我们要进行一些变换了:(我们将子节点简化为son)
- 当son的度=1时(son为终端的时候)dp[root] += dege(root,son) :没有该节点没有son了。
- 当son的度>1时dp[root] += min( dp[son],e(root,son) ) :
首先是+=,因为可能有多个son,要做一个循环累加;然后右式的意思是选取,每一个son的最小流,和能通过的流量中的较小值。(因为超过了流量也流不过去)
每个节点为根节点产生的最大流(Flow数组)获得:
- 怎么来的呢,我们可以简单地推出来:当子节点变成根时,该子树的最大流不变,由于倒置,父节点将会产生一个新的流:Flow[x]−min(Flow[y],edge[x][y])。(x原来的总流-y所在的流)
- 亮出我们的最终方程Flow[y]=Flow[y]+min(Flow[x]−min(Flow[y],edge[x][y]),edge[x][y])。
- 在图解里引用一下邓老师的图,超好理解。
图解
- 是求该分支的最大流。(圈起来的子树)
- 是求该节点做根节点时的最大流。(转化后的树)
AC代码
#include <iostream> #include <vector> using namespace std; #define js ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); const int MAX = 2e5 + 7; typedef struct { int bro, weight;//与该节点相连的节点,边的权重 } Node; vector<Node> Tree[MAX];//树的孩子表示法 int Flow[MAX], dp[MAX], degree[MAX], visited[MAX]; //Flow[i]表示以i为根的最大流量;dp[i]为i的子树的最大流量;degree表示每个结点的度;visited是探测数组 void dfs(int x);//初始化depth数组和cnt数组 void solve(int x);//求出每个点做根时的最大流量(F) int main() { js; int T; cin >> T; while (T--) { int n; cin >> n; memset(degree, 0, sizeof(degree)); memset(visited, 0, sizeof(visited)); for (int i = 1; i <= n; i++) Tree[i].clear(); //初始化 for (int i = 0; i < n - 1; i++) { int x, y, z; cin >> x >> y >> z; degree[x]++; degree[y]++; Tree[x].push_back({ y, z }); Tree[y].push_back({ x, z }); } //树的孩子表示法初始化 dfs(1); memset(visited, 0, sizeof(visited)); Flow[1] = dp[1]; solve(1); //计算出每个节点做根的情况存入Flow int max = 1; for (int i = 2; i <= n; i++) if (Flow[max] < Flow[i]) max = i; //选出其中的最大值 cout << Flow[max] << endl; } return 0; } void dfs(int x) { visited[x] = 1; dp[x] = 0; for (auto c : Tree[x]) if (!visited[c.bro]) { dfs(c.bro); if (degree[c.bro] == 1) dp[x] += c.weight; else dp[x] += min(c.weight, dp[c.bro]); //分类讨论的原因是,度为1的节点的dp值为0,和取较小值互斥 } } void solve(int x) { visited[x] = 1; for (auto c : Tree[x]) if (!visited[c.bro]) { if (degree[x] == 1) Flow[c.bro] = dp[c.bro] + c.weight; else Flow[c.bro] = dp[c.bro] + min(Flow[x] - min(dp[c.bro], c.weight), c.weight); //将某个节点变成根,并计算的操作 solve(c.bro); } }