https://atcoder.jp/contests/aising2019/tasks/aising2019_e
前提知識
解説
https://atcoder.jp/contests/aising2019/submissions/3989621
二乗の木DPというものがあり、それを利用する。
dp[cu][cut] := 頂点cuを根とした部分木について辺の削除をcut回行ったときに{根と連結している頂点の電力の総和の最小値, 根と連結しているグループが全てバッテリーにできるか}
このDPは二乗の木DPするなら、こういう感じかなという感じで作った、
二乗の木の条件として「dp[cu][cut]で、頂点cuの部分木の頂点数がsz[cu]のとき、cut≦sz[cu]であるとき」がある。
今回の問題では部分木のサイズ-1の辺しかcutできないため、cut≦sz[cu]は満たされている。
なので、二乗の木DPをする。
int N, A[5010]; vector<int> E[5010]; //--------------------------------------------------------------------------------------------------- vector<pair<ll, int>> dp[5010]; int sz[5010]; void marge(int cu, int to) { vector<pair<ll, int>> res(sz[cu] + sz[to] + 1, { infl,0 }); rep(cui, 0, sz[cu]) rep(toi, 0, sz[to]) { // くっつける chmin(res[cui + toi].first, dp[cu][cui].first + dp[to][toi].first); if (dp[cu][cui].second and dp[to][toi].second) res[cui + toi].second = 1; // くっつけない if (dp[to][toi].first < 0 or dp[to][toi].second) chmin(res[cui + toi + 1].first, dp[cu][cui].first); if ((dp[to][toi].first < 0 or dp[to][toi].second) and dp[cu][cui].second) res[cui + toi + 1].second = 1; } swap(dp[cu], res); sz[cu] += sz[to] + 1; } //--------------------------------------------------------------------------------------------------- void dfs(int cu, int pa = -1) { sz[cu] = 1; dp[cu].resize(1); dp[cu][0] = { A[cu], 0 < A[cu] }; fore(to, E[cu]) if (to != pa) { dfs(to, cu); marge(cu, to); } } //--------------------------------------------------------------------------------------------------- void _main() { cin >> N; rep(i, 0, N) cin >> A[i]; rep(i, 0, N - 1) { int a, b; cin >> a >> b; a--; b--; E[a].push_back(b); E[b].push_back(a); } dfs(0); int ans = inf; rep(i, 0, sz[0]) if (dp[0][i].first < 0 or dp[0][i].second) chmin(ans, i); cout << ans << endl; }