UVa First Bangladeshi Contest of 2012-2013 Season Problem (D) Draw and Score

問題

二分木を根のノードから書いていく。
二分木がバランスしているとは、
根が2つの子を持ち、それぞれの子を根とする部分木の大きさが等しいことを言う。


ノードを書いた後で、どれかを根とする部分木がバランスしたとき、
スコアに1点を加算する。
(複数の部分木がバランスした場合はその個数だけ点数を加算する)


N個のノードを書くとき、得られる最大の点数はいくつか、求めよ。

制約条件

N≦10^15

方針

根の左の子の個数をl, 右の子の個数をrとすると、
木が(根で)バランスする回数は最大でmin(l, r)回。


lを0からN-1まで試すと、左右の子のバランスする回数は
同じ問題で部分問題になるので、再帰により最大のスコアが求められる。


この再帰はメモ化して計算量がO(N^2).
OEISで回数の数列を調べたら、
0からNまでを二進法で書いたときに出現する1の数 - Nというのがこの数列の一般項らしい。
なのでそれをDPで求める。


このDPは典型的な桁DP.
(今までの合計と、場合の数を覚えておいて、今までの合計に加えて、場合の数x新たに加わる数を足していくようなDP)

ソースコード

int n, d[70];
ll dp[71][2], way[71][2];

int main()
{
	int CS;
	scanf("%d", &CS);
	
	rep(cs, CS){
		ll n, N;
		scanf("%lld", &n);
		memset(way, 0, sizeof(way));
		memset(dp, 0, sizeof(dp));
		
		N = n;
		rep(i, 70) d[69 - i] = n % 2, n /= 2;
		
		way[0][0] = 1;
		rep(i, 70) rep(s, 2) if(way[i][s]){
			rep(j, 2){
				int ns = s || j < d[i];
				if(!ns && j > d[i]) continue;
				way[i + 1][ns] += way[i][s];
				dp[i + 1][ns] += dp[i][s] + j * way[i][s];
			}
		}
		ll ans = dp[70][0] + dp[70][1] - N;
		
		printf("Case %d: %lld\n", cs + 1, ans);
	}
	
	return 0;
}