Codeforces 429(#245 Div1) C. Guess the Tree

問題

n頂点からなる木で、各頂点以下の部分木のサイズがc[i]であるような木であり、
葉以外の頂点では直接の子が二つ以上のものが存在するか、
存在するならYESを、そうでないならNOを出力せよ。

制約条件

n≦24

方針

DP.
葉と内点を区別して考える。
内点の出次数は2以上なので、
|葉| = Σ|内点の出次数| - (|内点| - 1)
|内点| + |葉| ≧ |内点| * 2 + 1
23 / 2≧|内点|
より内点は11個以下。


dp[i][j]を、根がi個ある森で、使う内点の頂点の集合がjであるようなもののうち、
葉の個数の最小であるものにおける葉の個数とする。


これを更新するDPをして、根に足りない葉をつけられるかみればよい。
更新は、今までの森に一つ木を足すか、一つの頂点に森をくっつけて木にするかのどちらか。

ソースコード

int n, out;
vi in;

int dp[12][1 << 11];

bool check(){
	
	rep(i, n + 1) rep(j, 1 << n) dp[i][j] = inf;
	rep(i, n) if(in[i] >= 3) dp[1][1 << i] = in[i];
	
	rep(i, 1 << n){
		rep(j, n) for(int k = (i - 1) & i; k; k = (k - 1) & i){
			
			if(dp[j][k] < inf && dp[1][i ^ k] < inf)
				dp[j + 1][i] = min(dp[j + 1][i], dp[j][k] + dp[1][i ^ k]);
		}
		rep(j, n) if(!(i & 1 << j)) rep(k, n){
			
			if(in[j] - 1 < dp[k][i]) continue;
			if(in[j] - 1 - dp[k][i] + k < 2) continue;
			
			dp[1][i | 1 << j] = in[j];
		}
	}
	return dp[1][(1 << n) - 1] < inf && in[n - 1] == n + out;
}

int main(){
	cin >> n;
	rep(i, n){
		int x; cin >> x;
		if(x == 1) out++;
		else in.pb(x);
	}
	if(n == out || in.size() > 11){
		cout << (n == 1 ? "YES" : "NO") << endl;
		return 0;
	}
	
	sort(all(in));
	n = in.size();
	
	cout << (check() ? "YES" : "NO") << endl;
	
	return 0;
}