|
|
|
@ -570,105 +570,46 @@ signed main() {
|
|
|
|
|
#### [$CF1324F$.$Maximum$ $White$ $Subtree$](https://codeforces.com/problemset/problem/1324/F)
|
|
|
|
|

|
|
|
|
|
|
|
|
|
|

|
|
|
|
|
|
|
|
|
|
我们在代码中将黑色记为$-1$,白色记为$1$
|
|
|
|
|
> **注**:因为$cnt_1-cnt_2$是所求:
|
|
|
|
|
> - 如果是白色,$cnt_1=1,cnt_2=0$,$cnt_1-cnt_2=1-0=1$
|
|
|
|
|
> - 如果是黑色,$cnt_1=0,cnt_2=1$,$cnt_1-cnt_2=0-1=-1$
|
|
|
|
|
> 所以,可以理解为黑色记为$-1$,白色记为$1$。
|
|
|
|
|
|
|
|
|
|
则有:
|
|
|
|
|
$$\large f_1[u]=c[u]+\sum_{fa[v] = u}max(f_1[v],0)$$
|
|
|
|
|
> **解释**
|
|
|
|
|
> ① $u$有很多儿子,其中某一个是$v$,对于以$v$为根的子树而言,如果它里面的$cnt_1-cnt_2>0$,则对于$u$而言,儿子$v$可以贡献$+(cnt_1-cnt_2)$的价值。
|
|
|
|
|
如果$cnt_1-cnt_2<=0$,对于$u$而言,不搭理它就完了。题目中取最大值,就是有一个选还是不选的抉择问题:对我有益我要,对我无益我弃!所有好儿子给的钱累加到一起,都是我的钱。
|
|
|
|
|
> ② $c[u]$可以理解为老头子自己的积蓄,除了儿子们给的,还要加上自己的老本。
|
|
|
|
|
**思路分析**
|
|
|
|
|
这题要求的是求出对任何一个节点$v$,求出包含这个节点的子树$cnt_1−cnt_2$的最大值。
|
|
|
|
|
|
|
|
|
|
我们记$vis[u]$表示在计算节点$u$的父亲的$f_1$值时,节点$u$是否有被选,$1$表示被选,$0$表示未选。
|
|
|
|
|
#### 暴力想法
|
|
|
|
|
首先思考下暴力写法应该如何写。
|
|
|
|
|

|
|
|
|
|
|
|
|
|
|
定义$f_2[u]$表示在整棵树中选一张包含$u$的连通子图的所有方案中,$cnt_1-cnt_2$的最大值。
|
|
|
|
|
对于所有可能的路径的贡献值的累加,且贡献值需大于等于$0$。
|
|
|
|
|
>- 白的比黑的多,有分, 这时我们选上这棵子树
|
|
|
|
|
>- 黑的比白的多,没分, 这时我们放弃这棵子树
|
|
|
|
|
|
|
|
|
|
考虑$f_2[u]$的组成:一部分是以其为根的子树,即$f_1[u]$;另一部分即是在全局中将其子树挖掉后剩下的部分。这两部分互相独立,分别取最大值即可。
|
|
|
|
|
不妨设$f[u]$代表$u$结点的最大值。故
|
|
|
|
|
|
|
|
|
|
考虑第二部分的计算:记$v$的父节点为$u$:
|
|
|
|
|
- ① 若$st[v]=1$,则其包含在$f_1[u]$中,故这一部分即为$f_2[u]-f_1[v]$
|
|
|
|
|
- ② 若$st[v]=0$,其不包含在$f_1[u]$中,则这一部分为$f_1[u]$。
|
|
|
|
|
最后将这一部分的值与$0$取$max$即可。
|
|
|
|
|
$$\large f[u]=c[u]+\sum_{v \in son_u}max(0,f[v])$$
|
|
|
|
|
|
|
|
|
|
所以整个算法只需要两遍$dfs$,第一遍自底向上计算出$f_1$数组,第二遍以$f_2[1]$(强制$1$为根节点)自上向下计算出$f_2$数组,即为答案。
|
|
|
|
|
假如用暴力写法,就是对于每个结点$u$,暴力搜索所有的相邻结点,利用$dfs$暴力搜索。也就是以每个结点为棵出发,枚举$n$次$dfs$,但是结点最大为$2∗10^5$ 这个暴力算法显然会超时,考虑如何优化。
|
|
|
|
|
|
|
|
|
|
```cpp {.line-numbers}
|
|
|
|
|
#include <bits/stdc++.h>
|
|
|
|
|
using namespace std;
|
|
|
|
|
const int N = 2e5 + 10, M = N << 1;
|
|
|
|
|
#### 算法优化
|
|
|
|
|
对于从下往上的贡献,可以利用从下往上的$dfs$树形$dp$进行获取,剩余的就是刨去以$v$为根的子树的贡献值比较难求。
|
|
|
|
|
|
|
|
|
|
// 链式前向星
|
|
|
|
|
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++;
|
|
|
|
|
}
|
|
|
|
|
在这里我们设$u$为结点$v$的父节点。$f[v]$代表从下往上以$v$为根的白点数减去黑点数的最大值,$dp[v]$代表最终的最大值。因此根据刨去以$v$为根的子树的贡献值这个思想,我们可以发现:
|
|
|
|
|
|
|
|
|
|
int f1[N];
|
|
|
|
|
int f2[N];
|
|
|
|
|
int st[N];
|
|
|
|
|
int c[N]; // 颜色
|
|
|
|
|
int n; // 节点数量
|
|
|
|
|
$$\large add=dp[u]−max(0,f[v])$$
|
|
|
|
|
|
|
|
|
|
// 以1号节点为根,跑一遍dfs,填充每个节点的cnt1-cnt2的最大值
|
|
|
|
|
void dfs1(int u, int fa) {
|
|
|
|
|
f1[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);
|
|
|
|
|
if (f1[v] > 0) { // 如果儿子能给点钱,那我就拿着
|
|
|
|
|
st[v] = 1; // v这个儿子给了它爸爸u钱
|
|
|
|
|
f1[u] += f1[v]; // 钱要累加到一起
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
就是刨去以$v$为根的子树的贡献值。因此最终我们可以写出状态转移方程:
|
|
|
|
|
$$\large dp[v] =
|
|
|
|
|
\left\{\begin{matrix}
|
|
|
|
|
f[v] & if \ v = root \\
|
|
|
|
|
f[v]+max(0,dp[fa]-max(0,f[v]))& if \ v \neq root
|
|
|
|
|
\end{matrix}\right.
|
|
|
|
|
$$
|
|
|
|
|
|
|
|
|
|
// 换根dp
|
|
|
|
|
void dfs2(int u, int fa) {
|
|
|
|
|
for (int i = h[u]; ~i; i = ne[i]) {
|
|
|
|
|
int v = e[i];
|
|
|
|
|
if (v == fa) continue;
|
|
|
|
|
// 如果 st[v]=1,说明v的贡献包含在f1[u]里面
|
|
|
|
|
int val = f2[u] - (st[v] ? f1[v] : 0);
|
|
|
|
|
f2[v] = f1[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);
|
|
|
|
|
}
|
|
|
|
|
因此最后我们的思路为:
|
|
|
|
|
|
|
|
|
|
// 第一次dfs
|
|
|
|
|
dfs1(1, 0);
|
|
|
|
|
- 从下往上树形$dp$,计算$f[v]$
|
|
|
|
|
- 从上往下换根$dp$,计算$dp[v]$
|
|
|
|
|
|
|
|
|
|
// 它们两个是一个意思
|
|
|
|
|
f2[1] = f1[1];
|
|
|
|
|
// 换根dp
|
|
|
|
|
dfs2(1, 0);
|
|
|
|
|
// 输出答案
|
|
|
|
|
for (int i = 1; i <= n; i++) printf("%d ", f2[i]);
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
[USACO12FEB]Nearby Cows G
|
|
|
|
|
|
|
|
|
|
[COCI2014-2015#1]Kamp
|
|
|
|
@ -679,6 +620,7 @@ POJ3585 Accumulation Degree
|
|
|
|
|
|
|
|
|
|
CF708C Centroids
|
|
|
|
|
|
|
|
|
|
Eg3: AT Educational DP Contest V-Subtree
|
|
|
|
|
|
|
|
|
|
#### [$AcWing$ $1073$. 树的中心](https://www.cnblogs.com/littlehb/p/15786805.html)
|
|
|
|
|
|
|
|
|
|