Loading

「刷题记录」LOJ/一本通提高篇 树型动态规划

「二叉苹果树」

题目传送门:二叉苹果树
状态:
\(dp(i, j)\) : 以 \(i\) 为根节点的子树选 \(j\) 条枝条的最大价值
\(dp(i, j) = \max(dp(i, j), dp(i, j - k - 1) + dp(son_i, k) + w)\)
树上背包

点击查看代码
/*
  date: 2022.9.2
  worked by yi_fan0305
 */
#include <iostream>
#include <cstdio>
using namespace std;
typedef long long ll;

const int N = 300;
int n, m, cnt;
int h[N], dp[N][N], sz[N];

struct edge {
	int u, v, nxt;
	ll w;
} e[N];

inline ll read() {
	ll x = 0;
	int fg = 0;
	char ch = getchar();
	while (ch < '0' || ch > '9') {
		fg |= (ch == '-');
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9') {
		x = (x << 3) + (x << 1) + (ch ^ 48);
		ch = getchar();
	}
	return fg ? ~x + 1 : x;
}

void add(int u, int v, ll w) {
	e[++cnt].u = u;
	e[cnt].v = v;
	e[cnt].w = w;
	e[cnt].nxt = h[u];
	h[u] = cnt;
}

void dfs(int u, int fat) {
	for (int i = h[u]; i; i = e[i].nxt) {
		int v = e[i].v, w = e[i].w;
		if(v == fat)
			continue;
		dfs(v, u);
		sz[u] += (sz[v] + 1);
		for (int j = min(m, sz[u]); j; --j) {
			for (int k = min(j - 1, sz[v]); k >= 0; --k) {
				dp[u][j] = max(dp[u][j], dp[u][j - k - 1] + dp[v][k] + w);
			}
		}
	}
}

int main() {
	n = read(), m = read();
	for (int u, v, i = 1; i < n; ++i) {
		u = read(), v = read();
		ll w = read();
		add(u, v, w);
		add(v, u, w);
	}
	dfs(1, 0);
	printf("%d\n", dp[1][m]);
	return 0;
}

「选课」

题目传送门:选课
状态:
\(dp(i, j)\):在以 \(i\) 为根的子树中选 \(j\) 门课
\(dp(i, j) = \max(dp(i, j), dp(son_i, k) + dp(i, j - k))\)
树上背包

点击查看代码
/*
  date: 2022.9.2
  worked by yi_fan0305
 */
#include <iostream>
#include <cstdio>
using namespace std;
typedef long long ll;

const int N = 310;
int n, m, cnt;
int h[N], dp[N][N], siz[N];

struct edge {
	int v, nxt, w;
} e[N << 1];

inline ll read() {
	ll x = 0;
	int fg = 0;
	char ch = getchar();
	while (ch < '0' || ch > '9') {
		fg |= (ch == '-');
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9') {
		x = (x << 3) + (x << 1) + (ch ^ 48);
		ch = getchar();
	}
	return fg ? ~x + 1 : x;
}

void add(int u, int v) {
	e[++cnt].v = v;
	e[cnt].nxt = h[u];
	h[u] = cnt;
}

void dfs(int u) {
	siz[u] = 1;
	for (int i = h[u]; i; i = e[i].nxt) {
		int v = e[i].v;
		dfs(v);
		siz[u] += siz[v];
		for (int j = min(n, siz[u]); j >= 1; --j) {
			for (int k = min(j - 1, siz[v]); k >= 0; --k) {
				dp[u][j] = max(dp[u][j], dp[v][k] + dp[u][j - k]);
			}
		}
	}
}

int main() {
	m = read(), n = read();
	for (int v, i = 1; i <= m; ++i) {
		v = read(), dp[i][1] = read();
		add(v, i);
	}
	++n;
	dfs(0);
	printf("%d\n", dp[0][n]);
	return 0;
}

「数字转换」

题目传送门:数字转换
求树的直径,预处理出能相互转换的数字

点击查看代码
/*
  date: 2022.9.2
  worked by yi_fan0305
 */
#include <iostream>
#include <cstdio>
using namespace std;
typedef long long ll;

const int N = 5e4 + 5;
int n, cnt, k, maxn;
int sum[N], h[N];

struct edge {
	int v, nxt;
} e[N << 1];

inline ll read() {
	ll x = 0;
	int fg = 0;
	char ch = getchar();
	while (ch < '0' || ch > '9') {
		fg |= (ch == '-');
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9') {
		x = (x << 3) + (x << 1) + (ch ^ 48);
		ch = getchar();
	}
	return fg ? ~x + 1 : x;
}

void add(int u, int v) {
	e[++cnt].v = v;
	e[cnt].nxt = h[u];
	h[u] = cnt;
}

void init() {
	for (int i = 1; i <= n; ++i) {
		if (sum[i] <= i && sum[i] != 0) {
			add(i, sum[i]);
			add(sum[i], i);
		}
		for (int j = (i << 1); j <= n; j += i) {
			sum[j] += i;
		}
	}
}

void dfs(int u, int fat, int len) {
	if (len > maxn) {
		k = u;
		maxn = len;
	}
	for (int i = h[u]; i; i = e[i].nxt) {
		int v = e[i].v;
		if (v == fat)
			continue;
		dfs(v, u, len + 1);
	}
}

int main() {
	n = read();
	init();
	dfs(1, 0, 0);
	maxn = 0;
	dfs(k, 0, 0);
	printf("%d\n", maxn);
	return 0;
}

「战略游戏」

题目传送门:战略游戏
状态:
\(dp(i, 0/1)\):第 \(i\) 个位置放不放士兵,\(0\) 代表不放,\(1\) 代表放
\(dp(i, 0) += dp(son_i, 1)\)
\(dp(i, 1) += \max(dp(son_i, 0), dp(son_i, 1))\)

点击查看代码
/*
  date: 2022.9.2
  worked by yi_fan0305
 */
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
typedef long long ll;

const int N = 1505;
int n, cnt;
int h[N], dp[N][2], son[N];

struct edge {
	int v, nxt;
} e[N << 1];

inline ll read() {
	ll x = 0;
	int fg = 0;
	char ch = getchar();
	while (ch < '0' || ch > '9') {
		fg |= (ch == '-');
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9') {
		x = (x << 3) + (x << 1) + (ch ^ 48);
		ch = getchar();
	}
	return fg ? ~x + 1 : x;
}

void add(int u, int v) {
	e[++cnt].v = v;
	e[cnt].nxt = h[u];
	h[u] = cnt;
}

void dfs(int u, int fat) {
	for (int i = h[u]; i; i = e[i].nxt) {
		int v = e[i].v;
		if (v == fat)
			continue;
		dfs(v, u);
		dp[u][0] += dp[v][1];
		dp[u][1] += min(dp[v][0], dp[v][1]);
	}
	++dp[u][1];
}

int main() {
	n = read();
	for (int i = 1; i <= n; ++i) {
		int u = read() + 1;
		son[u] = read();
		for (int j = 1; j <= son[u]; ++j) {
			int v = read() + 1;
			add(u, v);
			add(v, u);
		}
	}
	dfs(1, 0);
	printf("%d\n", min(dp[1][0], dp[1][1]));
	return 0;
}

「皇宫看守」

题目传送门:皇宫看守
状态:
\(dp(i,0/1/2)\): 在 \(i\) 位置上以及周围位置上是否有士兵
\(0\) 表示 \(i\) 位置上没有士兵,且父亲也没放
\(1\) 表示 \(i\) 位置上有士兵
\(2\) 表示 \(i\) 的父亲放了士兵
\(dp(i, 1) += \min(\min(dp(v, 0), dp(v, 1)), dp(v, 2))\)
\(dp(i, 2) += \min(dp(v, 0), dp(v, 1))\)
如果 \(i\) 位置没放士兵,则他至少要有一个孩子放置士兵,最初的 \(dp\)
\(dp(i, 0) += \min(dp(v, 0), dp(v, 1))\)
然后再扫一遍孩子
如果有 \(dp(i, 1) < dp(i, 0)\) 的情况,直接退出即可
否则,按照转移方程就是孩子们没有一个防止士兵,在扫的过程中,我们记录一下差 \(dp(v, 1) - dp(v, 0)\),将这些差取一个最小值 \(\min\),最后在加入到 \(dp(i,0)\)

点击查看代码
/*
  date: 2022.9.2
  worked by yi_fan0305
 */
#include <iostream>
#include <cstdio>
using namespace std;
typedef long long ll;

const int N = 1510;
const int inf = ~(1 << 31);
int n, cnt;
int w[N], h[N], dp[N][3];

struct edge {
	int v, nxt;
} e[N << 1];

inline ll read() {
	ll x = 0;
	int fg = 0;
	char ch = getchar();
	while (ch < '0' || ch > '9') {
		fg |= (ch == '-');
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9') {
		x = (x << 3) + (x << 1) + (ch ^ 48);
		ch = getchar();
	}
	return fg ? ~x + 1 : x;
}

void add(int u, int v) {
	e[++cnt].v = v;
	e[cnt].nxt = h[u];
	h[u] = cnt;
}

void dfs(int u, int fat) {
	int fg = 0, cha = inf;
	for (int i = h[u]; i; i = e[i].nxt) {
		int v = e[i].v;
		if(v == fat)
			continue;
		dfs(v, u);
		dp[u][1] += min(min(dp[v][1], dp[v][0]), dp[v][2]);
		dp[u][0] += min(dp[v][1], dp[v][0]);
		dp[u][2] += min(dp[v][1], dp[v][0]);
	}
	dp[u][1] += w[u];
	for (int i = h[u]; i; i = e[i].nxt) {
		int v = e[i].v;
		if(v == fat)
			continue;
		if(dp[v][1] < dp[v][0]) {
			fg = 1;
			break;
		}
		else {
			cha = min(cha, dp[v][1] - dp[v][0]);
		}
	}
	if(!fg) {
		dp[u][0] += cha;
	}
}

int main() {
	n = read();
	for (int i = 1, u, k; i <= n; ++i) {
		u = read();
		w[u] = read();
		k = read();
		for (int j = 1, v; j <= k; ++j) {
			v = read();
			add(u, v);
			add(v, u);
		}
	}
	dfs(1, 0);
	printf("%d\n", min(dp[1][0], dp[1][1]));
	return 0;
}

「加分二叉树」

题目传送门:加分二叉树
状态:
\(dp(i, j)\): \((i - j)\) 的最大得分
$dp(i, j) = \max(dp(i, j), dp(i, k - 1) + dp(k, k) + dp(k + 1, j)) $

点击查看代码
/*
  date: 2022.9.2
  worked by yi_fan0305
 */
#include <iostream>
#include <cstdio>
using namespace std;
typedef long long ll;

const int N = 50;
int n;
int dp[N][N], root[N][N];

inline ll read() {
	ll x = 0;
	int fg = 0;
	char ch = getchar();
	while (ch < '0' || ch > '9') {
		fg |= (ch == '-');
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9') {
		x = (x << 3) + (x << 1) + (ch ^ 48);
		ch = getchar();
	}
	return fg ? ~x + 1 : x;
}

void print(int l, int r) {
	if (l > r)
		return ;
	int k = root[l][r];
	printf("%d ", k);
	if (l == r)
		return ;
	print(l, k - 1);
	print(k + 1, r);
}

int main() {
	n = read();
	for (int i = 1; i <= n; ++i) {
		dp[i][i] = read();
		root[i][i] = i;
		dp[i][i - 1] = 1;
	}
	for (int len = 1; len < n; ++len) {
		for (int l = 1; l + len <= n; ++l) {
			int r = l + len;
			dp[l][r] = dp[l + 1][r] + dp[l][l];
			root[l][r] = root[l][l];
			for (int k = l + 1; k < r; ++k) {
				if(dp[l][r] < dp[l][k - 1] * dp[k + 1][r] + dp[k][k]) {
					dp[l][r] = dp[l][k - 1] * dp[k + 1][r] + dp[k][k];
					root[l][r] = k;
				}
			}
		}
	}
	printf("%d\n", dp[1][n]);
	print(1, n);
	return 0;
}

「旅游规划」

题目传送门:旅游规划
求树的直径,具体看代码

点击查看代码
/*
  date: 2022.9.2
  worked by yi_fan0305
 */
#include <iostream>
#include <cstdio>
using namespace std;
typedef long long ll;

const int N = 2e5 + 5;
int n, cnt, maxn, k;
int h[N], d1[N], d2[N], pos[N], up[N];

struct edge {
	int v, nxt;
} e[N << 1];

inline ll read() {
	ll x = 0;
	int fg = 0;
	char ch = getchar();
	while (ch < '0' || ch > '9') {
		fg |= (ch == '-');
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9') {
		x = (x << 3) + (x << 1) + (ch ^ 48);
		ch = getchar();
	}
	return fg ? ~x + 1 : x;
}

void add(int u, int v) {
	e[++cnt].v = v;
	e[cnt].nxt = h[u];
	h[u] = cnt;
}

void dfs(int u, int fat, int len) {
	if (len > maxn) {
		maxn = len;
		k = u;
	}
	for (int i = h[u]; i; i = e[i].nxt) {
		int v = e[i].v;
		if (v == fat)
			continue;
		dfs(v, u, len + 1);
	}
}

void tredp_down(int u, int fat) {
	for (int i = h[u]; i; i = e[i].nxt) {
		int v = e[i].v;
		if (v == fat)
			continue;
		tredp_down(v, u);
		if (d1[v] + 1 > d1[u]) {
			d2[u] = d1[u];
			d1[u] = d1[v] + 1;
			pos[u] = v;
		}
		else {
			if (d1[v] + 1 > d2[u]) {
				d2[u] = d1[v] + 1;
			}
		}
	}
}

void tredp_up(int u, int fat) {
	for (int i = h[u]; i; i = e[i].nxt) {
		int v = e[i].v;
		if (v == fat)
			continue;
		up[v] = up[u] + 1;
		if (v == pos[u]) {
			up[v] = max(up[v], d2[u] + 1);
		}
		else {
			up[v] = max(up[v], d1[u] + 1);
		}
		tredp_up(v, u);
	}
}

int main() {
	n = read();
	for (int i = 1, u, v; i < n; ++i) {
		u = read() + 1, v = read() + 1;
		add(u, v);
		add(v, u);
	}
	dfs(1, 0, 0);
	maxn = 0;
	dfs(k, 0, 0);
	tredp_down(1, 0);
	tredp_up(1, 0);
	for (int i = 1; i <= n; ++i) {
		if (d1[i] + d2[i] == maxn || d1[i] + up[i] == maxn)
			printf("%d\n", i - 1);
	}
	return 0;
}

「周年纪念晚会」

题目传送门:周年纪念晚会
状态:
$dp(i, 0/1) $: 第 \(i\) 个人去不去,\(0\) 表示不去,\(1\) 表示去
\(dp(i, 0) += \max(dp(v, 0), dp(v, 1))\)
\(dp(i, 1) += dp(v, 0)\)

点击查看代码
/*
  date: 2022.9.3
  worked by yi_fan0305
 */
#include <iostream>
#include <cstdio>
using namespace std;
typedef long long ll;

const int N = 6010;
int n, cnt;
int happy[N], h[N], dp[N][2];

struct edge {
	int v, nxt;
} e[N << 1];

inline ll read() {
	ll x = 0;
	int fg = 0;
	char ch = getchar();
	while (ch < '0' || ch > '9') {
		fg |= (ch == '-');
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9') {
		x = (x << 3) + (x << 1) + (ch ^ 48);
		ch = getchar();
	}
	return fg ? ~x + 1 : x;
}

void add(int u, int v) {
	e[++cnt].v = v;
	e[cnt].nxt = h[u];
	h[u] = cnt;
}

void dfs(int u, int fat) {
	for (int i = h[u]; i; i = e[i].nxt) {
		int v = e[i].v;
		if (v == fat)
			continue;
		dfs(v, u);
		dp[u][1] += dp[v][0];
		dp[u][0] += max(dp[v][1], dp[v][0]);
	}
}

int main() {
	n = read();
	for (int i = 1; i <= n; ++i) {
		dp[i][1] = read();
	}
	int l, k;
	while ((l = read()) && (k = read())) {
		add(l, k);
		add(k, l);
	}
	dfs(1, 0);
	printf("%d\n", max(dp[1][1], dp[1][0]));
	return 0;
}

「叶子的染色」

题目传送门:叶子的染色
状态:
$dp(i, 0/1) $:第 \(i\) 片叶子被染色,\(0\) 代表白色,\(1\)代表黑色
\(dp(i, 0) += \max(dp(v, 0) - 1, dp(v, 1))\)
\(dp(i, 1) += \max(dp(v, 1) - 1, dp(v, 0))\)

点击查看代码
/*
  date: 2022.9.3
  worked by yi_fan0305
 */
#include <iostream>
#include <cstdio>
using namespace std;
typedef long long ll;

const int N = 1e4 + 5;
const int inf = ~(1 << 31);
int n, m, cnt, rt;
int col[N], h[N], dp[N][2];

struct edge {
	int v, nxt;
} e[N << 1];

inline ll read() {
	ll x = 0;
	int fg = 0;
	char ch = getchar();
	while (ch < '0' || ch > '9') {
		fg |= (ch == '-');
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9') {
		x = (x << 3) + (x << 1) + (ch ^ 48);
		ch = getchar();
	}
	return fg ? ~x + 1 : x;
}

void add(int u, int v) {
	e[++cnt].v = v;
	e[cnt].nxt = h[u];
	h[u] = cnt;
}

void dfs(int u, int fat) {
	if (u <= n)
		return ;
	for (int i = h[u]; i; i = e[i].nxt) {
		int v = e[i].v;
		if (v == fat)
			continue;
		dfs(v, u);
		dp[u][0] += min(dp[v][0] - 1, dp[v][1]);
		dp[u][1] += min(dp[v][1] - 1, dp[v][0]);
	}
}

int main() {
	m = read();
	n = read();
	rt = n + 1;
	for (int i = 1; i <= n; ++i) {
		col[i] = read();
	}
	for (int i = 1, x, y; i < m; ++i) {
		x = read();
		y = read();
		add(x, y);
		add(y, x);
	}
	for (int i = 1; i <= m; ++i) {
		dp[i][1] = dp[i][0] = 1;
		if (i <= n) {
			dp[i][!col[i]] = inf;
		}
	}
	dfs(rt, 0);
	printf("%d\n", min(dp[rt][1], dp[rt][0]));
	return 0;
}

「骑士」

题目传送门:骑士
状态:
\(dp(i, 0/1)\):第 \(i\) 个骑士选不选,\(0\) 代表不选,\(1\) 代表选
\(\text{dalao}\) 的题解:题解

点击查看代码
/*
  date: 2022.9.3
  worked by yi_fan0305
 */
#include <iostream>
#include <cstdio>
using namespace std;
typedef long long ll;

const int N = 1e6 + 5;
int n, cnt;
ll ans;
int vi[N], fa[N], h[N], vis[N];
ll val[N], dp[N][2];// 1 选 0 不选

struct edge {
	int v, nxt;
} e[N << 1];

inline ll read() {
	ll x = 0;
	int fg = 0;
	char ch = getchar();
	while (ch < '0' || ch > '9') {
		fg |= (ch == '-');
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9') {
		x = (x << 3) + (x << 1) + (ch ^ 48);
		ch = getchar();
	}
	return fg ? ~x + 1 : x;
}

void add(int u, int v) {
	e[++cnt].v = v;
	e[cnt].nxt = h[u];
	h[u] = cnt;
}

void dfs(int u) {
	vi[u] = 1;// 避免重判
	dp[u][1] = val[u];
	for (int i = h[u]; i; i = e[i].nxt) {
		int v = e[i].v;
		if (vi[v]) 
			continue;
		dfs(v);
		dp[u][0] += max(dp[v][0], dp[v][1]);
		dp[u][1] += dp[v][0];
	}
}

void find(int u) {
	int rt;
	for (rt = u; !vis[rt]; rt = fa[rt]) {
		vis[rt] = 1;
	}
	dfs(rt);
	u = fa[rt];
	dp[u][1] = dp[u][0];// 强制不选 u 点
	for (u = fa[u]; u != rt; u = fa[u]) {// 不选 u 点,更新一遍
		dp[u][1] = val[u];
		dp[u][0] = 0;
		for (int i = h[u]; i; i = e[i].nxt) {
			int v = e[i].v;
			dp[u][1] += dp[v][0];
			dp[u][0] += max(dp[v][1], dp[v][0]);
		}
	}
	dp[rt][1] = val[u];
	for (int i = h[rt]; i; i = e[i].nxt) {// 重搜一遍
		int v = e[i].v;
		dp[rt][1] += dp[v][0];
	}
	ans += max(dp[rt][0], dp[rt][1]);
}

int main() {
	n = read();
	for (int i = 1; i <= n; ++i) {
		val[i] = read();
		fa[i] = read();
		add(fa[i], i);
	}
	for (int i = 1; i <= n; ++i) {
		if (!vi[i]) {
			find(i);
		}
	}
	printf("%lld\n", ans);
	return 0;
}
posted @ 2022-09-03 14:20  yi_fan0305  阅读(38)  评论(0编辑  收藏  举报