Luogu P5391 Solution
闲话
这道题目之所以被称为“青染之心”,不仅是因为这道题与东方有关的故事背景,更是因为这道题将我们的思绪拨回了刚学习树链剖分的时候。
题解
这道题的做法并不显然,毕竟这道题可以让人一眼想到可撤销背包,但是想到操作树远没有这么简单。即使知道了可以使用操作树处理,也很难想到使用树链剖分进行优化。更进一步地,即使知道了树链剖分,也不一定能跳出先处理重链的惯性思维,而改用先处理轻链。
此处具体解释如何通过重链剖分优化本题的空间,关于背包的部分不再赘述。
首先,让我们回想重链剖分拥有的一条重要性质——在一棵有 \(n\) 个节点的树上,从根到任意节点的唯一路径上的轻边数不超过 \(\log n\)。
我第一次看到此题时,首先想到的思路是使用重链剖分,从根节点所在的重链开始,一条一条链地处理,中间碰到轻边就将整个 dp 数组拷贝一份等到后面处理。
然而,重链剖分并不保证一棵树最多被剖分成 \(\log n\) 条链。一个节点数 \(n\) 不少于 \(2\) 的菊花图会被剖分成 \((n-1)\) 条链。
于是,在这道题中,我们需要一反常态地先处理轻边。
具体来说,对于操作树上的一个节点 \(x\),我们将挂载在节点 \(x\) 上的物品加入背包后,我们可以先将 dp 数组复制到轻边所连接的节点,并立即递归处理。原因是,在处理完任意一个轻链后,我们可以直接回收这条轻链所占的 dp 数组空间,并将其赋予下一个轻链。
令物品总数为 \(n\),则时间复杂度 \(O(nV)\),空间复杂度 \(O(V\log n)\)。
代码
#include <cctype>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <string>
using namespace std;
const int N = 2e4 + 10;
int n, q, v, dp[15][N], siz[N], ds[N], idx, res[N], tag[N], f[N], tx[N], ty[N];
basic_string<int> road[N];
template <typename _Tp> inline void read(_Tp &x)
{
char ch;
while (ch = getchar(), !isdigit(ch) and ~ch)
;
x = (ch ^ 48);
while (ch = getchar(), isdigit(ch))
x = (x << 3) + (x << 1) + (ch ^ 48);
}
#define max(x, y) ((x) > (y) ? (x) : (y))
template <typename _Tp, typename... _Args> inline void read(_Tp &x, _Args &...args)
{
read(x);
read(args...);
}
void init(int x)
{
siz[x] = 1;
ds[x] = n + 1;
for (auto &i : road[x])
{
init(i);
if (siz[i] > siz[ds[x]])
ds[x] = i;
siz[x] += siz[i];
}
}
void dfs(int x)
{
for (int i = tx[x]; i <= v; i++)
{
dp[idx][i] = max(dp[idx][i], dp[idx][i - tx[x]] + ty[x]);
res[x] = max(res[x], dp[idx][i]);
}
res[x] = max(res[x], res[f[x]]);
if (!ds[x])
return;
for (auto &i : road[x])
{
if (i == ds[x])
continue;
idx++;
memcpy(dp[idx] + 1, dp[idx - 1] + 1, v << 2);
dfs(i);
idx--;
}
dfs(ds[x]);
}
string s;
int main()
{
read(q, v);
for (int i = 1, ta, tb; i <= q; i++)
{
tag[i] = tag[i - 1];
cin >> s;
if (s == "erase")
{
tag[i] = f[tag[i]];
continue;
}
read(ta, tb);
n++;
tx[n] = ta, ty[n] = tb;
road[tag[i]] += n;
f[n] = tag[i];
tag[i] = n;
}
init(0);
dfs(0);
for (int i = 1; i <= q; i++)
{
printf("%d\n", res[tag[i]]);
}
}