「刷题记录」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;
}