1. 程式人生 > 實用技巧 >題解-CF429C Guess the Tree

題解-CF429C Guess the Tree

題面

CF429C Guess the Tree

給一個長度為 \(n\) 的陣列 \(a_i\),問是否有一棵樹,每個節點要麼是葉子要麼至少有兩個兒子,而且 \(i\) 號點的子樹大小是 \(a_i\)

資料範圍:\(1\le n\le 24\)


題解

發現 \(n\) 很小,想到可以狀壓。

設葉子節點有 \(ln\) 個,所以中間節點有 \(mn=n-ln\) 個。

由於“每個節點要麼是葉子要麼至少有兩個兒子”,所以 \(ln\ge\lceil\frac n2\rceil\)\(mn\le n-\lceil\frac n2\rceil \le 11\)

所以可以先特判 \(2mn\ge n\)

的情況答案為 NO

然後剩下的可以 dp\(f_{t,s,i}\)

\(t\) 表示是一棵森林還是一個子樹(為了對付“每個節點要麼是葉子要麼至少有兩個兒子”,如果是子樹 \(t=0\),否則 \(t=1\))。

\(s\) 表示包含的中間節點集合,\(s< 2^{mn}\le 2048\)

\(i\) 表示包含的葉子節點個樹,因為葉子節點都是一樣的,所以這樣可以優化狀壓。

值表示是否存在這樣的森林,如果存在 \(=1\),否則 \(=0\)

考慮怎麼轉移:

  1. 一棵森林(子樹)和另一棵森林(子樹)合併成新的森林。

  2. 一棵森林上面加一個 \(a=\) 森林大小 \(+1\) 的節點成為一棵子樹(怎麼求一棵森林的大小?其實就是 \({\rm popcount}(s)+i\)

    啦)。

然後就剩初始化的問題了,因為 \(i\) 這維就像一個揹包,而且因為 \(s\) 這一維保證不會有重點,所以可以在 dp 中用無限揹包的方式,這樣就只需要初始化 \(f_{0,0,1}=1\) 了。

細節看程式碼。


程式碼

#include <bits/stdc++.h>
using namespace std;

//Start
typedef long long ll;
typedef double db;
#define mp(a,b) make_pair((a),(b))
#define x first
#define y second
#define bg begin()
#define ed end()
#define sz(a) int((a).size())
#define pb(a) push_back(a)
#define R(i,a,b) for(int i=(a),i##E=(b);i<i##E;i++)
#define L(i,a,b) for(int i=(b)-1,i##E=(a)-1;i>i##E;i--)
const int iinf=0x3f3f3f3f;
const ll linf=0x3f3f3f3f3f3f3f3f;

//Data
const int N=24,mN=11;
int n,sn,ln,mn,a[N],b[1<<mN];
int f[2][1<<mN][N+1]; // 森林還是子樹,中間節點集,葉子節點數
bool get(int s,int i){ // 返回 f[1][s][i] 的值
    for(int su=s;su;su=s&(su-1))if(!(su&(s^su)))R(j,0,i+1)
        if((f[0][su][j]||f[1][su][j])&&(f[0][s^su][i-j]||f[1][s^su][i-j])) return true;
    R(j,0,i+1)if((f[0][0][j]||f[1][0][j])&&(f[0][s][i-j]||f[1][s][i-j])) return true; // 因為 su!=0,補上迴圈中缺少的 su=0
    return false;
}

//Main
int main(){
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    cin>>n; R(i,0,n) cin>>a[i]; sort(a,a+n,greater<int>());
    R(i,0,n)if(a[i]>1) mn++; ln=n-mn,sn=1<<mn,sort(a,a+mn); 
    if(mn*2>=n) cout<<"NO\n",exit(0);
    R(i,1,sn) b[i]=b[i>>1]+(i&1);
    f[0][0][1]=true;
    R(s,0,sn)R(i,0,ln+1)if(get(s,i)){
        f[1][s][i]=true;
        R(t,0,mn)if(!(s&(1<<t))&&a[t]==b[s]+i+1) // 加新的 a= 森林大小+1 的節點形成子樹
            f[0][s^(1<<t)][i]=true;
    }
    if(f[0][sn-1][ln]) cout<<"YES\n";
    else cout<<"NO\n";
    return 0;
}

祝大家學習愉快!