题解:P12734 理解

· · 题解

我最倾慕的学长退役后的最后一篇题解写的这题,时隔数月我也来做一下这题,对我们一起学习的日子以示怀念。

思路

由于每个事件至多只有一个前置事件,因此事件之间的关系构成一片森林。

考虑到不同儿子子树之间不存在联想关系,且可以免费忘记事件,因此可以依次处理各个儿子子树。又求最小代价,看上去这是个树上最优化问题,所以考虑怎么设计树形 DP。

大概是这样的思维链(有点啰嗦,懒得看可以直接跳到设计那里)。

DP 的值必然是表示完成 u 子树内所有问题的最小代价。

受到最大记忆数量 k 的限制,所以专门需要一维表示子树内的记忆的占用情况。

粗略的想还应该要一维表示当前这个节点是否占用大脑的位置。一方面解决必须记起的事件,一方面用于提供转移的信息。

但是进一步观察如果根本不记起 u,就不需要给以 u 为起点的联想过程分配容量,因为不同的联想连通块可以依次处理,所以只需要考虑与 u 联想连通的部分占用的记忆容量。如果不记起 u,就可以独立处理各个儿子子树。所以可以合并容量限制和当前点是否要记忆的信息。只要容量参数大于等于 1 就代表着当前点是一定要记忆的。

但是现在又有一个问题,当前的代价(处理子树的根)受到它父节点选不选的影响。一般可能会想到再加一维规定父节点的选择情况。但实际上父节点是否被记起,并不会改变 u 子树内部(不含 u)的最优处理方式,只会影响进入这棵子树的方式和可用容量。所以我们在处理父节点的时候再决定儿子的转移。

设 f_{u,0} 表示不记起 u,完成其子树内所有题目的最小代价(特别的,如果 u 是必须记起的事件,

设 $f_{u,j}$($1\le j\le k$)表示记起 $u$,且 $u$ 子树内与 $u$ 联想连通的部分最多同时占用 $j$ 个记忆位置的最小代价。 注意 $f_{u,j}$ **不包含记起 $u$ 本身的代价**。 对于 $u$ 的儿子 $v$,定义: $$ A_v=\min(f_{v,0},r_v+f_{v,k}) $$ 表示不通过 $u$ 联想 $v$ 的最小代价。 分类讨论一下转移: - $j=0$ 儿子只能选择回忆或者不回忆: $$f_{u,0}=\sum_{v\in son(u)}A_v$$ - $j=1$ 只有一个记忆位置,无法通过 $u$ 联想儿子,因此: $$f_{u,1}=\sum_{v\in son(u)}A_v$$ - $2\le j\le k$ 对于儿子 $v$,可以选择不通过 $u$ 联想,或者通过 $u$ 联想: $$B_v=\min(A_v,t_v+f_{v,j-1})$$ 由于处理最后一个通过 $u$ 联想的儿子时,可以在记起 $v$ 后立即忘记 $u$,因此该儿子可以使用 $j$ 个记忆位置。 枚举最后一个通过 $u$ 联想的儿子,得到: $$f_{u,j}=\sum_{v\in son(u)}B_v+\min\left(0,\min_{v\in son(u)}(t_v+f_{v,j}-B_v)\right)$$ 对于每棵树的根节点 $u$,只能选择不记起或直接回想: $$ ans=\sum_{p_u=0}\min(f_{u,0},r_u+f_{u,k}) $$ 由于 $p_i<i$,可以直接按照节点编号从大到小进行转移。 时间复杂度和空间复杂度都是 $O(nk)$。 ## 代码 因为我做完才想起 $p_i<i$,所以我还是建树跑递归了。 ```cpp #include <bits/stdc++.h> using namespace std; typedef long long ll; const int N = 1e5 + 5, K = 11; const ll inf = 4e18; int n, m, k; ll r[N], t[N], dp[N][K]; int p[N]; vector<int> tr[N]; void dfs(int u, int fa) { for (int v : tr[u]) { dfs(v, u); ll A = min(dp[v][0], dp[v][k] + r[v]); dp[u][0] += A; dp[u][1] += A; } for (int j = 2; j <= k; j++) { ll mnB = 0; for (int v : tr[u]) { ll A = min(dp[v][0], dp[v][k] + r[v]); // 不回忆/回忆 ll B = min(A, dp[v][j - 1] + t[v]); // A/联想 dp[u][j] += B; mnB = min(mnB, dp[v][j] + t[v] - B); } dp[u][j] += mnB; } } void solve() { cin >> n >> m >> k; for (int i = 1; i <= n; i++) tr[i].clear(); memset(dp, 0, sizeof(dp)); for (int i = 1; i <= n; i++) { cin >> p[i]; tr[p[i]].push_back(i); } for (int i = 1; i <= n; i++) cin >> r[i]; for (int i = 1; i <= n; i++) cin >> t[i]; for (int i = 1; i <= m; i++) { int x; cin >> x; dp[x][0] = inf; } ll ans = 0; for (int i = 1; i <= n; i++) { if (!p[i]) { dfs(i, 0); ans += min(dp[i][0], r[i] + dp[i][k]); } } cout << ans << "\n"; } int main() { cin.tie(0)->sync_with_stdio(0); int T; cin >> T; while (T--) solve(); return 0; } ```