[CCPC2022 广州站]A.Alice and Her Lost Cat
Description
给定 \(n\) 个节点的树,每个点上都有监控。根节点为 \(1\)。叶子节点为非根且度为 \(1\) 的点。
Alice 的猫从根节点跑到了一个叶子节点,没有一个节点被经过超过 \(1\) 次。Alice 想要找到她的猫,但她还不确定猫究竟跑到了哪里。
调用 \(i\) 节点的监控代价为 \(a_i\),调用了此监控后,可以了解到猫是否经过了 \(i\) 节点,如果 \(i\) 不是叶子节点,那么可以查看到猫走向了 \(i\) 的哪个儿子。Alice 还可以选择单独检查 \(k\) 个叶子节点,代价为 \(t_k\)(注意 \(k\) 是节点的数量)。
Alice 需要提前确定她执行的操作,以便无论猫跑到了哪里,她都可以精确地知晓猫的位置。注意,你需要在检查之前确定好要调用的监控和要检查的节点,付出一定代价,然后再真正地调用监控并检查节点。
\(1\le n \le 2000\),\(1 \le a_i,t_i\le 10^9\),\(\forall 1\le i < n,t_i \le t_{i+1}\)。
Solution
约定:\(p_i\) 表示 \(i\) 子树内叶子结点的个数。
容易发现检查叶子节点的代价可以与监控代价分开算。一个最初的想法是定义 \(f_{i,j}\) 表示 \(i\) 子树以确定了 \(j\) 个叶子的有无猫所付出的最小代价。答案就是 \(\displaystyle\min_{0 \le i \le p_1}\{f_{1,i}+t_{p_1-i}\}\)。这样的定义对于转移来说信息太少了,难以转移。
观察性质,如果我们调用了 \(u\) 节点的监控,那么 \(u\) 子树就可以且仅能少检查一个叶子。若 \(u\) 节点共有 \(l\) 个叶子,若猫在 \(u\) 子树内,那我们只需检查 \(u\) 子树中的 \(l-1\) 个叶子,若这 \(l-1\) 个叶子都没猫,那么剩下一个一定有猫。为 \(f_{i,j}\) 增设一维 \(0/1\) 表示是否存在一个叶子需要祖先来确定。
考虑两种情况。这里的 \(\gets\) 表示取较小值。
-
Case 1:不调用 \(u\) 节点的监控,则:
\[\begin{aligned} &\underline{f_{u,k,0}\gets\min_{v\in\mathrm{son}(u)\land\sum\limits k_v=k}\left\{\sum f_{v,k_v,0}\right\}}\\ &f_{u,k,1}\gets\min_{v\in\mathrm{son}(u)\land\sum\limits k_v=k \land \sum o_v=1}\left\{\sum f_{v,k_v,o_v}\right\} \end{aligned} \]其中 \(o_v \in \{0,1\},k_v \in [0,p_v]\)。模仿树形背包转移这一段即可,复杂度为 \(\mathcal{O}(n^2)\)。
-
Case 2:调用 \(u\) 节点的监控:
\(u\) 节点调用监控后,就可以了解到猫走向了哪个儿子,因此儿子的所有等待祖先检查的叶子就成功获得了检查。
\[f_{u,k,0} \gets a_u+\min_{v\in\mathrm{son}(u)\land\sum\limits k_v=k}\left\{\sum \min\{f_{v,k_v,0},f_{v,k_v,1}\}\right\} \]实现时,单独开一个 \(g_{u,k}\) 表示后半部分的 \(\min\) 值。\(g_{u,k}\) 用正常的树上背包转移即可。
总的时间复杂度为 \(\mathcal{O(n^2)}\)。
Code
#include <bits/stdc++.h>
#define FASTIO ios::sync_with_stdio(0), cin.tie(0), cout.tie(0)
#define rep(i, l, r) for (int i = l; i <= r; i++)
#define per(i, r, l) for (int i = r; i >= l; i--)
using namespace std;
typedef long long LL;
const int N = 2005, M = 4e5 + 5, mod = 998244353;
int n, a[N], t[N], sz[N];
LL f[N][N][2], g[N][N];
vector<int> G[N];
#define adde(u, v) G[u].push_back(v)
void dfs(int x, int fa) {
if (G[x].size()==1 && fa) {
sz[x] = 1, f[x][1][0] = a[x];
f[x][0][0] = 0, f[x][1][1] = 0;
return;
}
sz[x] = 0, f[x][0][0] = 0, g[x][0] = 0;
for (auto y : G[x]) {
if (y == fa) continue;
dfs(y, x);
per(i, sz[x], 0)
per(j, sz[y], 0) {
int to = i+j;
f[x][to][0] = min(f[x][to][0], f[x][i][0]+f[y][j][0]);
f[x][to][1] = min({f[x][to][1], f[x][i][1]+f[y][j][0], f[x][i][0]+f[y][j][1]});
g[x][to] = min(g[x][to], g[x][i]+min(f[y][j][0], f[y][j][1]));
}
sz[x] += sz[y];
}
rep(i, 1, sz[x]) f[x][i][0] = min(f[x][i][0], g[x][i]+a[x]);
}
inline void solve() {
cin >> n;
memset(f, 0x3f, sizeof f);
memset(g, 0x3f, sizeof g);
rep(i, 1, n) G[i].clear();
rep(i, 1, n) cin >> a[i];
rep(i, 1, n) cin >> t[i];
rep(i, 2, n) {
int u,v;
cin >> u >> v;
G[u].push_back(v), G[v].push_back(u);
}
if (n == 1) return void(cout << "0\n");
dfs(1, 0);
LL ans = LONG_LONG_MAX;
rep(i, 1, n) ans = min(ans, min(f[1][i][0], f[1][i][1])+t[sz[1]-i]);
cout << ans << '\n';
}
int main() {
FASTIO;
int _;
cin >> _;
while (_--) solve();
return 0;
}

浙公网安备 33010602011771号