main
黄海 2 years ago
parent 9380564e25
commit bab3ee9676

@ -1,55 +1,65 @@
#include <bits/stdc++.h> #include <bits/stdc++.h>
using namespace std; using namespace std;
using VI = vector<int>; const int N = 2e5 + 10, M = N << 1;
using VVI = vector<VI>;
// 链式前向星
VI a; int e[M], h[N], idx, w[M], ne[M];
VI dp; void add(int a, int b, int c = 0) {
VI ans; e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx++;
VVI e; }
void dfs(int x, int fa = -1) { int f[N];
dp[x] = a[x]; int g[N];
for (int to : e[x]) { int c[N]; // 颜色
if (to == fa) continue; int n; // 节点数量
dfs(to, x);
dp[x] += max(0, dp[to]); // 以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) { // 换根dp
ans[x] = dp[x]; void dfs2(int u, int fa) {
for (int to : e[x]) { for (int i = h[u]; ~i; i = ne[i]) {
if (to == fa) continue; int v = e[i];
dp[x] -= max(0, dp[to]); if (v == fa) continue;
dp[to] += max(0, dp[x]); int val = g[u] - max(f[v], 0);
rdfs(to, x); g[v] = f[v] + max(val, 0);
dp[to] -= max(0, dp[x]); dfs2(v, u);
dp[x] += max(0, dp[to]);
} }
} }
int main() { int main() {
int n; // 初始化链式前向星
memset(h, -1, sizeof h);
cin >> n; cin >> n;
a = dp = ans = VI(n);
e = VVI(n);
for (int i = 0; i < n; ++i) { for (int i = 1; i <= n; i++) {
cin >> a[i]; int x;
if (a[i] == 0) a[i] = -1; cin >> x;
c[i] = (x ? x : -1); // 白色c[i]=1黑色c[i]=-1
} }
for (int i = 0; i < n - 1; ++i) {
int x, y; for (int i = 1; i < n; i++) {
cin >> x >> y; int a, b;
--x, --y; cin >> a >> b;
e[x].push_back(y); add(a, b), add(b, a);
e[y].push_back(x);
} }
dfs(0);
rdfs(0);
for (int ret : ans) cout << ret << " "; // 第一次dfs
cout << endl; dfs1(1, 0);
// 它们两个是一个意思
g[1] = f[1];
// 换根dp
dfs2(1, 0);
// 输出答案
for (int i = 1; i <= n; i++) printf("%d ", g[i]);
return 0; return 0;
} }

@ -591,25 +591,96 @@ $$\large f[u]=c[u]+\sum_{v \in son_u}max(0,f[v])$$
假如用暴力写法,就是对于每个结点$u$,暴力搜索所有的相邻结点,利用$dfs$暴力搜索。也就是以每个结点为棵出发,枚举$n$次$dfs$,但是结点最大为$210^5$ 这个暴力算法显然会超时,考虑如何优化。 假如用暴力写法,就是对于每个结点$u$,暴力搜索所有的相邻结点,利用$dfs$暴力搜索。也就是以每个结点为棵出发,枚举$n$次$dfs$,但是结点最大为$210^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 add=g[u]max(0,f[v])$$
$$\large dp[v] = > **注**$fa[v]=u$
就是刨去以$v$为根的子树的贡献值。写出状态转移方程:
$$\large g[v] =
\left\{\begin{matrix} \left\{\begin{matrix}
f[v] & if \ v = root \\ 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. \end{matrix}\right.
$$ $$
因此最后我们的思路为: 因此思路:
- ① 从下往上树形$dp$,计算$f[v]$
- ② 从上往下换根$dp$,计算$g[v]$
**$Code$**
```cpp {.line-numbers}
#include <bits/stdc++.h>
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]$ // 第一次dfs
- 从上往下换根$dp$,计算$dp[v]$ 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 [USACO12FEB]Nearby Cows G
[COCI2014-2015#1]Kamp [COCI2014-2015#1]Kamp

Loading…
Cancel
Save