loading...

[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;
}
posted @ 2025-02-21 12:54  goldspade  阅读(36)  评论(0)    收藏  举报