AOJ 2344 Multi Ending Story

問題

二分木が与えられる。

  • 根に移動するコストが0
  • 子に移動するコストが0
  • 一つだけ、今いるノードを覚えることができて、覚えるコストが0
  • 覚えたノードに移動するコストが0
  • (何度でも新たに上書きで覚えることができる)

とき、全ての子を訪れるのにかかる最小コストを求めよ。

制約条件

n≦500000

方針

木DP.


頂点vにいるとき、vの左の子をl[v], 右の子をr[v]とする。
l[v]を頂点とする部分木のノードを全て訪問し終えた後でr[v]を頂点とする部分木のノードを初めて訪れるか、
r[v]を頂点とする部分木のノードを全て訪問し終えた後でl[v]を頂点とする部分木のノードを初めて訪れるような訪問のしかたしか最短ではない。


dp[0][v]を、vを頂点とする部分木を、セーブを使わないで全部訪問するのにかかるコスト、
dp[1][v]を、vを頂点とする部分木を、セーブを使って全部訪問するのにかかるコストとする。


dp[0][v]はすぐにもとまる。
dp[1][v]は、vでセーブして、左の部分木をセーブなしで全て訪れて、右の部分木を訪れるか、その逆の訪問の仕方がある。
もしくは、左の部分木をセーブありで訪れて、一度根まで戻って、右の部分木を訪れるという訪問の仕方がある。
dp[1][v]はこれらの最小値である。


再帰でやるとREなので適当にループで書き換える。

ソースコード

const int MX = 500001;
int n, l[MX], r[MX], lf[MX], depth[MX];
ll dp[2][MX];

inline void rec(int c, int d = 0){
	lf[c] += lf[l[c]] + lf[r[c]];
	dp[0][c] += dp[0][l[c]] + lf[l[c]];
	dp[0][c] += dp[0][r[c]] + lf[r[c]];
	
	dp[1][c] = min(lf[l[c]] + dp[0][l[c]] + dp[1][r[c]], lf[r[c]] + dp[1][l[c]] + dp[0][r[c]]) + 1;
	dp[1][c] = min(dp[1][c], 2 + d + dp[1][l[c]] + dp[1][r[c]]);
}

int main(){
	cin >> n;
	rep(i, n - 1) cin >> l[i + 1] >> r[i + 1];
	
	vi ord;
	queue<pi> q;
	q.push(mp(1, 0));
	while(!q.empty()){
		int c = q.front().first, d = q.front().second; q.pop();
		ord.pb(c);
		depth[c] = d;
		if(l[c] < n) q.push(mp(l[c], d + 1));
		if(r[c] < n) q.push(mp(r[c], d + 1));
	}
	reverse(all(ord));
	
	lf[n] = 1;
	rep(i, n - 1) rec(ord[i], depth[ord[i]]);
	cout << dp[1][1] << endl;
	return 0;
}