树形dp部分题目总结
树形dp还是太难了
No.1 P2664 树上游戏
题目直接点开即可,这里不再赘述
我们发现其实直接统计每条路径上的颜色个数并不好统计,即使拆开贡献也是如此
举个例子,你要统计一个节点的贡献,那么你的贡献区间是不好确定的,因为你并不能快速确定贡献区间端点的同色点都在那里,它们之中既可能包含祖先,也有可能包含祖先的某几个子孙节点
那么我们仔细想一想可以发现,反向来减去贡献似乎是好维护的?
我们选定一种颜色 \(c\) ,并将所有颜色为 \(c\) 的点全部删掉,那么我们就可以得到若干个联通块,显然每个联通块内任意两点之间都不存在贡献,反之则一定存在,我们利用树上差分维护一下每个联通块大小即可
// Problem: P2664 树上游戏
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P2664
// Memory Limit: 125 MB
// Time Limit: 1000 ms
// Author: Floyd.Uranus
//
// Powered by CP Editor (https://cpeditor.org)
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define int long long
const int N = 1e5 + 10;
#define Floyd main
int inline read () {
int x = 0, f = 1;
char ch = getchar ();
while (ch < '0' || ch > '9') {
if (ch == '-') f = -1;
ch = getchar ();
}
while (ch >= '0' && ch <= '9') {
x = x * 10 + ch - '0';
ch = getchar ();
}
return x * f;
}
inline void write (int x) {
if (x < 0) putchar ('-'), x = -x;
if (x > 9) write (x / 10);
putchar ('0' + x % 10);
return void ();
}
ll n, c[N], sum, cnt, vis[N], ans[N], siz[N], lz[N], bj[N], Minus[N];
vector <int> e[N];
void inline dfs (ll x, ll fa) {
siz[x] = 1;
ll tmp = Minus[c[fa]];
for (auto nxt : e[x]) {
if (nxt == fa) continue;
dfs (nxt, x);
siz[x] += siz[nxt];
}
Minus[c[x]] ++;
if (fa) lz[x] = siz[x] - Minus[c[fa]] + tmp, Minus[c[fa]] += lz[x];
return void ();
}
void inline getans (int x, int fa) {
ll ybj = bj[c[fa]];
cnt += lz[x] - bj[c[fa]];
bj[c[fa]] = lz[x];
ans[x] = n * sum - cnt + bj[c[x]];
for (auto nxt : e[x]) {
if (nxt == fa) continue;
getans (nxt, x);
}
bj[c[fa]] = ybj;
cnt -= lz[x] - bj[c[fa]];
return void ();
}
void inline solve () {
n = read();
for (int i = 1; i <= n; ++i) {
c[i] = read();
if (!vis[c[i]]) vis[c[i]] = 1, sum ++;
}
for (int i = 1; i < n; ++i) {
int a = read(), b = read();
e[a].push_back(b), e[b].push_back(a);
}
dfs (1, 0);
for (int i = 1; i <= 100000; ++i)
if (vis[i]) cnt += n - Minus[i], bj[i] = n - Minus[i];
getans (1, 0);
for (int i = 1; i <= n; ++i)
write (ans[i]),putchar ('\n');
return void ();
}
signed Floyd () {
int T = 1;
while (T--) solve ();
return 0;
}
No.2 P3748 [六省联考 2017] 摧毁“树状图”
这道题也是好起来了
比较显然的树形dp,就是状态比较难想,转移有点难推
不妨大胆假设它给出的三种情况对于我们最后的答案均无影响(也即我们只去考虑第一种情况)
敲黑板!状态设计
我们可以想一下,当前我们在一个节点 \(x\) 的时候,如果它的子树内部只有一条链,那么有几种情况?
显然是三种,一种是这条链的一端点为 \(x\) ;另一种是完全包含在某一个子树内部;还有一种就是两个端点都在子树内部,但是经过点 \(x\)
以此类推,我们可以推知两条链的情况
1.两个都完全包含在子树内
2.其中一个经过点 \(x\)
好的,我们接下来根据这个可以去设计一个相对来说比较好转移的状态了
设:
1.\(f_{p, 0}\) 表示点 \(p\) 子树内部含有一条链,且这条链的一个端点为 \(p\) 的答案
2.\(f_{p, 1}\) 表示点 \(p\) 子树内部含有一条链,且这条链并不经过点 \(p\) 的答案
3.\(f_{p, 2}\) 表示点 \(p\) 子树内部含有一条链,这条链经过点 \(p\) 但是 \(p\) 并不是端点的答案
4.\(f_{p, 3}\) 表示点 \(p\) 子树内部有两条链,一条以 \(p\) 为一个端点,另一条完全不经过点 \(p\) 的答案
怎么转移?别急,等我代码
敲黑板!状态转移!
先粘一下代码
MAX (ans, f[x][3] + f[nxt][0] - (x == 1));
MAX (ans, f[x][2] + f[nxt][2] - (x == 1));
MAX (ans, f[x][1] + f[nxt][1] - 1);
MAX (ans, f[x][0] + f[nxt][3] - (x == 1));
MAX (ans, f[x][2] + f[nxt][1] - (x == 1));
MAX (ans, f[x][1] + f[nxt][2]);
MAX (f[x][1], f[nxt][2] + 1);
MAX (f[x][1], f[nxt][1]);
MAX (f[x][3], f[nxt][3] + deg[x] - 1);
MAX (f[x][3], f[x][0] + f[nxt][2] - 1);
MAX (f[x][3], f[x][0] + f[nxt][1] - 1);
MAX (f[x][3], f[x][2] + f[nxt][0] - 1);
MAX (f[x][3], f[nxt][0] + deg[x] + ret - 2);
MAX (f[x][2], f[x][0] + f[nxt][0] - 1);
MAX (f[x][0], f[nxt][0] + deg[x] - 1);
MAX (ret, max (f[nxt][1], f[nxt][2]));
至于是怎么来的我就不细讲了,留作读者思考,画图会更好理解一些
其中 \(deg_p\) 的意思就是 \(p\) 的儿子数
于是我们就做完了
贴一下完整代码
// Problem: P3748 [六省联考 2017] 摧毁“树状图”
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P3748
// Memory Limit: 512 MB
// Time Limit: 1000 ms
// Author: Floyd.Uranus
//
// Powered by CP Editor (https://cpeditor.org)
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e5 + 10;
#define Floyd main
int inline read () {
int x = 0, f = 1;
char ch = getchar ();
while (ch < '0' || ch > '9') {
if (ch == '-') f = -1;
ch = getchar ();
}
while (ch >= '0' && ch <= '9') {
x = x * 10 + ch - '0';
ch = getchar ();
}
return x * f;
}
void inline write (int x) {
if (x < 0) putchar ('-'), x = -x;
if (x > 9) write (x / 10);
putchar ('0' + x % 10);
return void ();
}
int ans, f[N][4], deg[N], n, T, IDX;
vector <int> e[N];
void inline add (int x, int y) {
e[x].push_back(y), e[y].push_back(x);
return deg[x] ++, deg[y] ++, void ();
}
void inline Clear () {
for (int i = 1; i <= n; ++i) deg[i] = 0, e[i].clear ();
return void ();
}
void inline MAX (int &a, int b) {return a = max(a, b), void ();}
void inline dp (int x, int fa) {
int ret = 0;
for (auto nxt : e[x]) {
if (nxt == fa) continue;
dp (nxt, x);
MAX (ans, f[x][3] + f[nxt][0] - (x == 1));
MAX (ans, f[x][2] + f[nxt][2] - (x == 1));
MAX (ans, f[x][1] + f[nxt][1] - 1);
MAX (ans, f[x][0] + f[nxt][3] - (x == 1));
MAX (ans, f[x][2] + f[nxt][1] - (x == 1));
MAX (ans, f[x][1] + f[nxt][2]);
MAX (f[x][1], f[nxt][2] + 1);
MAX (f[x][1], f[nxt][1]);
MAX (f[x][3], f[nxt][3] + deg[x] - 1);
MAX (f[x][3], f[x][0] + f[nxt][2] - 1);
MAX (f[x][3], f[x][0] + f[nxt][1] - 1);
MAX (f[x][3], f[x][2] + f[nxt][0] - 1);
MAX (f[x][3], f[nxt][0] + deg[x] + ret - 2);
MAX (f[x][2], f[x][0] + f[nxt][0] - 1);
MAX (f[x][0], f[nxt][0] + deg[x] - 1);
MAX (ret, max (f[nxt][1], f[nxt][2]));
}
return void ();
}
void inline solve () {
ans = 0, n = read();
for (int i = 1; i <= IDX; ++i) read(), read();
for (int i = 1; i < n; ++ i) add (read(), read()), deg[i + 1] --;
for (int i = 1; i <= n; ++i)
f[i][0] = f[i][2] = f[i][3] = deg[i], f[i][1] = 1;
dp (1, 0);
write (ans), putchar ('\n');
Clear ();
return void ();
}
signed Floyd () {
T = read(), IDX = read();
while (T--) solve ();
return 0;
}

树形dp
浙公网安备 33010602011771号