From bab3ee96765396541b86d097a561941013c754e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E6=B5=B7?= <10402852@qq.com> Date: Wed, 10 Jan 2024 16:06:08 +0800 Subject: [PATCH] 'commit' --- TangDou/Topic/HuanGenDp/CF1324F.cpp | 90 ++++++++++++++------------ TangDou/Topic/【换根】dfs专题.md | 89 ++++++++++++++++++++++--- 2 files changed, 130 insertions(+), 49 deletions(-) diff --git a/TangDou/Topic/HuanGenDp/CF1324F.cpp b/TangDou/Topic/HuanGenDp/CF1324F.cpp index 6e9caba..1526161 100644 --- a/TangDou/Topic/HuanGenDp/CF1324F.cpp +++ b/TangDou/Topic/HuanGenDp/CF1324F.cpp @@ -1,55 +1,65 @@ #include using namespace std; -using VI = vector; -using VVI = vector; - -VI a; -VI dp; -VI ans; -VVI e; - -void dfs(int x, int fa = -1) { - dp[x] = a[x]; - for (int to : e[x]) { - if (to == fa) continue; - dfs(to, x); - dp[x] += max(0, dp[to]); +const int N = 2e5 + 10, M = N << 1; + +// 链式前向星 +int e[M], h[N], idx, w[M], ne[M]; +void add(int a, int b, int c = 0) { + e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx++; +} + +int f[N]; +int g[N]; +int c[N]; // 颜色 +int n; // 节点数量 + +// 以1号节点为根,跑一遍dfs,填充每个节点的cnt1-cnt2的最大值 +void dfs1(int u, int fa) { + f[u] = c[u]; // 1:白色,-1黑色,正好与 cnt1-cnt2一致,初始值加上了老头子自己的养老钱 + for (int i = h[u]; ~i; i = ne[i]) { + int v = e[i]; + if (v == fa) continue; + dfs1(v, u); + f[u] += max(0, f[v]); // 如果我儿子给我,那我就拿着;如果我儿子不给我钱,或者管我要钱,我就不理它! } } -void rdfs(int x, int fa = -1) { - ans[x] = dp[x]; - for (int to : e[x]) { - if (to == fa) continue; - dp[x] -= max(0, dp[to]); - dp[to] += max(0, dp[x]); - rdfs(to, x); - dp[to] -= max(0, dp[x]); - dp[x] += max(0, dp[to]); +// 换根dp +void dfs2(int u, int fa) { + for (int i = h[u]; ~i; i = ne[i]) { + int v = e[i]; + if (v == fa) continue; + int val = g[u] - max(f[v], 0); + g[v] = f[v] + max(val, 0); + dfs2(v, u); } } int main() { - int n; + // 初始化链式前向星 + memset(h, -1, sizeof h); cin >> n; - a = dp = ans = VI(n); - e = VVI(n); - for (int i = 0; i < n; ++i) { - cin >> a[i]; - if (a[i] == 0) a[i] = -1; + for (int i = 1; i <= n; i++) { + int x; + cin >> x; + c[i] = (x ? x : -1); // 白色c[i]=1,黑色c[i]=-1 } - for (int i = 0; i < n - 1; ++i) { - int x, y; - cin >> x >> y; - --x, --y; - e[x].push_back(y); - e[y].push_back(x); + + for (int i = 1; i < n; i++) { + int a, b; + cin >> a >> b; + add(a, b), add(b, a); } - dfs(0); - rdfs(0); - for (int ret : ans) cout << ret << " "; - cout << endl; + // 第一次dfs + dfs1(1, 0); + + // 它们两个是一个意思 + g[1] = f[1]; + // 换根dp + dfs2(1, 0); + // 输出答案 + for (int i = 1; i <= n; i++) printf("%d ", g[i]); return 0; -} +} \ No newline at end of file diff --git a/TangDou/Topic/【换根】dfs专题.md b/TangDou/Topic/【换根】dfs专题.md index fce948a..d20aa7a 100644 --- a/TangDou/Topic/【换根】dfs专题.md +++ b/TangDou/Topic/【换根】dfs专题.md @@ -591,25 +591,96 @@ $$\large f[u]=c[u]+\sum_{v \in son_u}max(0,f[v])$$ 假如用暴力写法,就是对于每个结点$u$,暴力搜索所有的相邻结点,利用$dfs$暴力搜索。也就是以每个结点为棵出发,枚举$n$次$dfs$,但是结点最大为$2∗10^5$ 这个暴力算法显然会超时,考虑如何优化。 #### 算法优化 - 对于从下往上的贡献,可以利用从下往上的$dfs$树形$dp$进行获取,剩余的就是刨去以$v$为根的子树的贡献值比较难求。 + 对于从下往上的贡献,可以利用从下往上的$dfs$树形$dp$进行获取,难求的是刨去以$v$为根的子树的贡献值,也就是向上走的那部分。 -在这里我们设$u$为结点$v$的父节点。$f[v]$代表从下往上以$v$为根的白点数减去黑点数的最大值,$dp[v]$代表最终的最大值。因此根据刨去以$v$为根的子树的贡献值这个思想,我们可以发现: +设$u$为节点$v$的父节点,$f[v]$代表从下往上以$v$为根的 **白点数减去黑点数** 的 **最大值**,$g[v]$代表最终的最大值。 -$$\large add=dp[u]−max(0,f[v])$$ +根据刨去以$v$为根的子树的贡献值这个思想,可以发现: -就是刨去以$v$为根的子树的贡献值。因此最终我们可以写出状态转移方程: -$$\large dp[v] = +$$\large add=g[u]−max(0,f[v])$$ +> **注**:$fa[v]=u$ + +就是刨去以$v$为根的子树的贡献值。写出状态转移方程: +$$\large g[v] = \left\{\begin{matrix} f[v] & if \ v = root \\ - f[v]+max(0,dp[fa]-max(0,f[v]))& if \ v \neq root + f[v]+max(0,g[u]-max(0,f[v]))& if \ v \neq root \end{matrix}\right. $$ -因此最后我们的思路为: +因此思路: + +- ① 从下往上树形$dp$,计算$f[v]$ +- ② 从上往下换根$dp$,计算$g[v]$ + +**$Code$** +```cpp {.line-numbers} +#include +using namespace std; +const int N = 2e5 + 10, M = N << 1; + +// 链式前向星 +int e[M], h[N], idx, w[M], ne[M]; +void add(int a, int b, int c = 0) { + e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx++; +} + +int f[N]; +int g[N]; +int c[N]; // 颜色 +int n; // 节点数量 + +// 以1号节点为根,跑一遍dfs,填充每个节点的cnt1-cnt2的最大值 +void dfs1(int u, int fa) { + f[u] = c[u]; // 1:白色,-1黑色,正好与 cnt1-cnt2一致,初始值加上了老头子自己的养老钱 + for (int i = h[u]; ~i; i = ne[i]) { + int v = e[i]; + if (v == fa) continue; + dfs1(v, u); + f[u] += max(0, f[v]); // 如果我儿子给我,那我就拿着;如果我儿子不给我钱,或者管我要钱,我就不理它! + } +} + +// 换根dp +void dfs2(int u, int fa) { + for (int i = h[u]; ~i; i = ne[i]) { + int v = e[i]; + if (v == fa) continue; + int val = g[u] - max(f[v], 0); + g[v] = f[v] + max(val, 0); + dfs2(v, u); + } +} + +int main() { + // 初始化链式前向星 + memset(h, -1, sizeof h); + cin >> n; + + for (int i = 1; i <= n; i++) { + int x; + cin >> x; + c[i] = (x ? x : -1); // 白色c[i]=1,黑色c[i]=-1 + } + + for (int i = 1; i < n; i++) { + int a, b; + cin >> a >> b; + add(a, b), add(b, a); + } -- 从下往上树形$dp$,计算$f[v]$ -- 从上往下换根$dp$,计算$dp[v]$ + // 第一次dfs + dfs1(1, 0); + // 它们两个是一个意思 + g[1] = f[1]; + // 换根dp + dfs2(1, 0); + // 输出答案 + for (int i = 1; i <= n; i++) printf("%d ", g[i]); + return 0; +} +``` [USACO12FEB]Nearby Cows G [COCI2014-2015#1]Kamp