ARC 029 D - 高橋君と木のおもちゃ

問題

n頂点からなる木がある。木には最初s[i]の値がかかれている。
次の操作をm回行う。


数字tiを手に入れる。捨てるか、好きな木の頂点cを選んで数字を置く。
頂点cにもともとあった数字はcの親に行く。cの親vにあった数字はvの親に行く。
……と同様に根までこの操作が続けられ、根にもともとあった数字は消える。


最後に木に残っている数字の和を最大化したい。
和の最大値を求めよ。

制約条件

n, m≦5000

方針

よくある木で配列を持ちまわすDP.


結果的にi個、元の数字を消すとするとき消す頂点の集合というのは根を含み、連結なってなければならない。
したがって根から消す頂点i個を、和が最小になりかつ連結になるよう選べばいいことがわかる。

愚直にdp[v][i]を、vの頂点以下i個を消すときの和の最小値というDPだとO(n^3)でTLE.
ここでdp[i]の配列を持ちまわすようなDPをするとO(n^2)でできる。


rec(頂点v, 親から持ってきたdp配列)という感じで再帰関数を書くことを考える。
まず、子にdp配列を投げるとき。
子に1つでも消す頂点があるときは、vも消さなければならないので、
子に投げる配列では必ずvを使うようにしなければならない。
で、配列を親に返すとき、各iで最小値のほうを採用すればよい。


なんかデータが弱くてO(n^3)でも通るらしい。
[追記]
データが弱いわけではなくて、自明なDPでもO(n^2)になるのだった。参考→http://topcoder.g.hatena.ne.jp/iwiwi/20120428/1335635594


T(l + r) = T(l) + T(r) + l * rの計算量はT(n) = O(n^2)。
T(n)≦cn^2とおいて帰納法すればよくて、T(l + r) = cl^2 + cr^2 + l*r ≦ c(l+r)^2 ≦ cn^2

ソースコード

int n, s[5000], m, t[5000];
vector<vi> e;

void rec(int c, ll *a){
	ll *b = new ll[n + 1];
	b[0] = inf;
	rep(i, n) b[i + 1] = a[i] + s[c];
	
	rep(i, e[c].size()) rec(e[c][i], b);
	rep(i, n + 1) a[i] = min(a[i], b[i]);
	
	delete [] b;
}

int main(){
	cin >> n;
	e.resize(n);
	rep(i, n) cin >> s[i];
	rep(i, n - 1){
		int a, b; cin >> a >> b;
		a--; b--;
		e[a].pb(b);
	}
	cin >> m;
	rep(i, m) cin >> t[i];
	sort(t, t + m, greater<int>());
	
	ll *a = new ll[n + 1];
	rep(i, n + 1) a[i] = inf; a[0] = 0;
	rec(0, a);
	
	ll ans = 0, sum = accumulate(s, s + n, 0ll);
	rep(i, n + 1){
		ans = max(ans, sum - a[i]);
		sum += t[i];
	}
	cout << ans << endl;
	
	return 0;
}