树形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;
}
posted @ 2025-10-30 20:32  Floyd_Hyacinth  阅读(3)  评论(0)    收藏  举报