From 232e9bfe5de62fd2aaa4f3ed52c93a57dbd043ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E6=B5=B7?= <10402852@qq.com> Date: Tue, 16 Jan 2024 11:44:51 +0800 Subject: [PATCH] 'commit' --- TangDou/Topic/HuanGenDp/POJ3585.cpp | 75 +++++++++++++++++ TangDou/Topic/【换根】dfs专题.md | 110 ++++++++++--------------- 2 files changed, 117 insertions(+), 68 deletions(-) create mode 100644 TangDou/Topic/HuanGenDp/POJ3585.cpp diff --git a/TangDou/Topic/HuanGenDp/POJ3585.cpp b/TangDou/Topic/HuanGenDp/POJ3585.cpp new file mode 100644 index 0000000..f56c9b1 --- /dev/null +++ b/TangDou/Topic/HuanGenDp/POJ3585.cpp @@ -0,0 +1,75 @@ +#include +#include +#include +#include +#define rg register +#define go(i, a, b) for (rg int i = a; i <= b; i++) +#define mem(a, b) memset(a, b, sizeof(a)) +using namespace std; +const int N = 2e5 + 2; +int T, n, du[N]; +struct node { + int to, w; +}; +vector c[N]; +bool v[N]; +int d[N], f[N]; +void clean() { + mem(v, 0); + mem(du, 0); + mem(d, 0); + mem(f, 0); + go(i, 1, n) c[i].clear(); +} +void dp(int x) { + v[x] = 1; + int size = c[x].size() - 1; + go(i, 0, size) { + int to = c[x][i].to, w = c[x][i].w; + if (v[to]) continue; + dp(to); + if (du[to] == 1) + d[x] += w; + else + d[x] += min(d[to], w); + } + return; +} +void dfs(int x) { + v[x] = 1; + int size = c[x].size() - 1; + go(i, 0, size) { + int to = c[x][i].to, w = c[x][i].w; + if (v[to]) continue; + if (du[x] == 1) + f[to] = d[to] + w; + else + f[to] = d[to] + min(f[x] - min(d[to], w), w); + dfs(to); + } + return; +} + +int main() { + scanf("%d", &T); + while (T--) { + scanf("%d", &n); + clean(); + int x, y, z; + go(i, 1, n - 1) { + scanf("%d%d%d", &x, &y, &z); + c[x].push_back((node){y, z}); + c[y].push_back((node){x, z}); + du[x]++; + du[y]++; + } + dp(1); + f[1] = d[1]; + mem(v, 0); + dfs(1); + int ans = 0; + go(i, 1, n) ans = max(ans, f[i]); + printf("%d\n", ans); + } + return 0; +} \ No newline at end of file diff --git a/TangDou/Topic/【换根】dfs专题.md b/TangDou/Topic/【换根】dfs专题.md index a09d8fa..8d4b954 100644 --- a/TangDou/Topic/【换根】dfs专题.md +++ b/TangDou/Topic/【换根】dfs专题.md @@ -822,102 +822,76 @@ int main() { return 0; } ``` -####[$CF708C$ $Centroids$](https://www.luogu.com.cn/problem/CF708C) - - -#### [$P6419$ $Kamp$](https://www.luogu.com.cn/problem/P6419) -**题目大意** - -一颗树 $n$ 个点,$n-1$ 条边,经过每条边都要花费一定的时间,任意两个点都是联通的。 - -有 $K$ 个人(分布在 $K$ 个不同的点)要集中到一个点举行聚会。 - -聚会结束后需要一辆车从举行聚会的这点出发,把这 $K$ 个人分别送回去。 - -请你回答,对于 $i=1 \sim n$ ,如果在第 $i$ 个点举行聚会,司机最少需要多少时间把 $K$ 个人都送回家。 - - -**思路** -换根$dp$的特征感觉还是非常明显的,就是答案 **可被移动** -具体点就是可以在$log_n$或者是$n$的时间内通过计算来把一个节点的答案转化得到另一个节点的答案 -这题就很明显,虽然要分类讨论,还有其他的挺多的限制和要求...主要是分类讨论很烦人。。 - -- 第一遍$dfs$直接把从每个点出发经过它子树内所有终点并返回的路径长度算出来 -这个是最基础的,还有一些其它要算的我后面会说 - -- 第二遍$dfs$就需要算出来全局的答案了,就是从一个点出发,**经过全图所有点并返回的路径长** - -如果有仔细一点,推过样例的同学就会发现,答案要求的不是这个,而是 **不返回的最短路径** -就是车车把最后一个人送回家后,离开停下,到此为止它开的最短路径 -这个路径和我们统计的路径有什么区别呢? -很明显,有一个容易证明的贪心,这个最短路径,就是我们上面算出来的路径减去这个点到离这个点最远的点路径长 -(因为不用返回嘛) +#### [$POJ3585$ $Accumulation$ $Degree$](http://poj.org/problem?id=3585) -那么,很明显,如果我们只是统计一个子树的根节点到这个子树内的终点的最短路径,这个在第一个dfs内就可以直接完成 -问题就在于,怎么计算整棵树范围的,也就是全图的答案。 +**题意** +一个树形水系,有$n$个结点,根结点称为 **源点**,叶子结点称为 **汇点**,每条边都有水量限制$C_{x,y}$($x,y$为这条边的两个端点),源点单位时间流出的水量称为整个水系的流量,求以哪一个结点作为源点整个水系的流量最大。 -假如这个子树的根节点就是这棵树的根,那很明显这个最短路径就是我们要的最短路径 -那我们在第一遍dfs的时候所用的根就是我们第二遍换根dp的起点 +![](https://dsideal.obs.cn-north-1.myhuaweicloud.com/HuangHai/BlogImages/202401161121787.png) -那我们现在要考虑的就是,已知一个节点作为跟的时候的答案,怎么把这个答案转移到其子节点上?也就是如何换根 -要分类讨论了 +```cpp {.line-numbers} +A(1)=11+5+8=24 +Details: 1->2 11 +  1->4->3 5 +  1->4->5 8(since 1->4 has capacity of 13) -首先我们以1号节点为根 +A(2)=5+6=11 +Details: 2->1->4->3 5 +  2->1->4->5 6 -f[u] 从点u出发把所有在u的子树里的人送回家并返回u的距离 +A(3)=5 +Details: 3->4->5 5 -ans[u]从点u出发把所有人出发并返回u的距离 +A(4)=11+5+10=26 +Details: 4->1->2 11 +  4->3 5 +  4->5 10 -dis[u] 从u出发,在u的子树内,距离u最远的那个人的家到u的距离 +A(5)=10 +Details: 5->4->1->2 10 +``` +**思路分析** -sdis[u] 从u出发,在u的子树内,距离u次远的那个人家到u的距离并且这个人的家和dis[u]不在同一个子树内(这个东西的作用后面会说到) +这是一道 **不定根** 的树形$DP$问题,这类题目的特点是,给定一个树形结构,需要以每个结点为根进行一系列统计。我们一般通过两次扫描来求解此类问题: -up[u] 不在u的子树内,距离u最远的那个人的家到u的距离 +- 1.第一次扫描,任选一个点为根,在 **有根树** 上执行一次树形$DP$。 +- 2.第二次扫描,从刚才选出的根出发,对整棵树执行一次$DFS$,在每次递归前进行 **自上而下** 的推导,计算出 **换根** 之后的解。 -siz[u] u及u的子树内有多少个人的家 -f[u],siz[u],dis[u],sdis[u]在第一遍dfs内就可以求出来 +首先,我们任选一个结点$root$,然后树形$DP$一下,求出$D_{root}$数组($D[i]$表示在以$i$为根的子树中流量的最大值)。然后设$f[x]$表示以$x$为源点,流向整个水系的最大流量,则显然 +$f[root]=D[root]$。假设$f[x]$已经求出,考虑其子结点$y$,则$f[y]$包含两部分: -关键的是up[u]和ans[u]的计算 +1.从$y$流向以$y$为根的子树的流量,已经计算出来。 +2.从$y$沿着到父节点$x$的河道,进而流向水系中其他部分的流量。 -设fa为u的父亲节点,w为u和fa之间的路径长度 +由题意可得,从$x$流向$y$的流量为$min(D[y],c[x][y])$,所以从$x$流向除$y$以外其他部分的流量就是二者之差$f[x]−min(D[y],c[x][y])$。于是,把$y$作为源点,先流到$x$,再流向其他部分的流量就是把这个“差”再与$c[x][y]$取较小值后的结果。 -ans[u]的计算规则 +$if(du[x]>1)→f[y]=D[y]+min(f[x]−min(D[y],c[x][y])−c[x][y])$ +$if(du[x]==1)→f[y]=D[y]+c[x][y]$ -1.当siz[u]==0时,ans[u]=ans[fa]+w*2 -2.当siz[u]==k(就是所有的重点都在这个子树内)时,ans[u]=f[u] -3.其他的情况,ans[u]=fa[u] +这是一个由上而下的递推方程,我们通过一次DFS即可实现。 -up[u]的计算规则就更复杂一些 -1.当siz[u]==0,up[u]=up[fa]+w +####[$CF708C$ $Centroids$](https://www.luogu.com.cn/problem/CF708C) +https://blog.csdn.net/TheSunspot/article/details/118216638 -2.当siz[u]==k,up[u]=0 +https://www.cnblogs.com/DongPD/p/17498336.html -3.其他情况 +#### [$P6419$ $Kamp$](https://www.luogu.com.cn/problem/P6419) +**题目大意** -  有两种,一种是最长的就是up[fa]+w -  一种是最长的要经过它的兄弟节点 -  第二种还有两种情况。。 -  如果dis[u]>up[u](就是上面说的第二种情况的一种)但是这个dis[u]实际上是经过了u的,那肯定是不能要的 -  但是它的兄弟节点的可能却没有被考虑到 -  所以对每一个节点就要记录一个最长路,和一个不和最长路相交的次长路,也就是dis[u]和sdis[u] +一颗树 $n$ 个点,$n-1$ 条边,经过每条边都要花费一定的时间,任意两个点都是联通的。 -那这样最后的答案就是ans[i]-max(up[i],dis[i]) +有 $K$ 个人(分布在 $K$ 个不同的点)要集中到一个点举行聚会。 -有一说一真的没有紫题难度。。不知道为什么是紫题 +聚会结束后需要一辆车从举行聚会的这点出发,把这 $K$ 个人分别送回去。 +请你回答,对于 $i=1 \sim n$ ,如果在第 $i$ 个点举行聚会,司机最少需要多少时间把 $K$ 个人都送回家。 #### [$P3647$ $APIO2014$ 连珠线](https://www.luogu.com.cn/problem/P3647) -#### [$POJ3585$ $Accumulation$ $Degree$](http://poj.org/problem?id=3585) -https://blog.csdn.net/qq_34493840/article/details/90575293 - - -#### [$CF708C$ $Centroids$](https://www.luogu.com.cn/problem/CF708C) -https://www.cnblogs.com/DongPD/p/17498336.html #### [Eg3: AT Educational DP Contest V-Subtree](https://dmoj.ca/problem/dpv)