0x50 動態規劃(0x5C 計數類DP)例題3:裝飾圍欄(題解)(計數類DP講解,確定第k個排列)

計數類DP一般就是確定DP狀態,DP出排名範圍,然後不斷逼近。

題意

題目鏈接

【題目描述】
 有 N 塊長方形的木板,長度分別爲1,2,…,N,寬度都是1。
 現在要用這 N 塊木板組成一個寬度爲 N 的圍欄,滿足在圍欄中,每塊木板兩側的木板要麼都比它高,要麼都比它低。
 也就是說,圍欄中的木板是高低交錯的。
 我們稱“兩側比它低的木板”處於高位,“兩側比它高的木板”處於低位。
 顯然,有很多種構建圍欄的方案。
 每個方案可以寫作一個長度爲N的序列,序列中的各元素是木板的長度。
 把這些序列按照字典序排序,如下圖所示,就是 N=4 時,所有滿足條件的圍欄按照木板長度的字典序排序後的結果。 

 現在給定整數C,求排名爲C的圍欄中,各木板的長度從左到右依次是多少。 
注:兩側的木板指的是相鄰的兩塊木板 
【輸入格式】
 第一行包含整數K,表示一共有K組數據。
 接下來K行,每行包含一組數據,包括兩個整數N和C。 
 【輸出格式】
 每組數據輸出一行結果,結果表示排名爲C的圍欄中,各木板的長度從左到右排成的序列。
 同行數據用空格隔開。 
 【數據範圍】
1<=N<=20
 0<C<2^63 
【輸入樣例】
2
 2 1
 3 3 
【輸出樣例】
1 2
 2 3 1 

題解

f[i][j][0/1]f[i][j][0/1],分別表示的是點集在11~(ni+1)(n-i+1),第一個位置選的是jj且第一個位置是down(0)/up(1)down(0)/up(1)的時候的方案數。

那麼很明顯的一個狀態轉移方程是:
f[i1][j][0]=k=jni+1f[i][k][1]f[i-1][j][0]=\sum\limits_{k=j}^{n-i+1}f[i][k][1]

意思就是把s(i)s(i)點集中的jj~ni+1n-i+111,把jj插到原本jj的位置上,就成了s(i1)s(i-1)的點集了。

那麼很顯然:
f[i1][j][1]=k=1i1f[i][k][0]f[i-1][j][1]=\sum\limits_{k=1}^{i-1}f[i][k][0]

然後我們可以對於第11位的數字,通過看排名範圍來確定第一位是什麼數字,upup還是downdown,然後後面也一樣慢慢逼近就行了。

#include<cstdio>
#include<cstring>
#define  N  30
using  namespace  std;
typedef  long  long  LL;
LL  f[N][N][2],m;//0爲up,1爲down
int  n; 
int  a[N];
inline  int  findkth(int  k)
{
    int  x=0;
    for(int  i=1;i<=n;i++)
    {
        x+=a[i];
        if(x==k)
        {
            a[i]=0;
            return  i;
        }
    }
}
int  b[N];
int  main()
{
    int  T;scanf("%d",&T);
    while(T--)
    {
        memset(f,0,sizeof(f));
        scanf("%d%lld",&n,&m);
        for(int  i=1;i<=n;i++)a[i]=1;
        f[n][1][0]=f[n][1][1]=1;
        for(int  i=n-1;i>=1;i--)
        {
            for(int  j=n-i+1;j>=1;j--)
            {
                //現在處理的是up的情況
                for(int  k=1;k<j;k++)f[i][j][1]+=f[i+1][k][0];
                for(int  k=n-i+1;k>=j;k--)f[i][j][0]+=f[i+1][k][1];
            }
        }
        //計數DP
        int  type,id;
        for(int  i=1;i<=n;i++)//確定第一位是多少,up還是down 
        {
            if(f[1][i][1]<m)m-=f[1][i][1];
            else{b[1]=findkth(i);id=i;type=1;break;}
            if(f[1][i][0]<m)m-=f[1][i][0];
            else{b[1]=findkth(i);id=i;type=0;break;}
        }
        for(int  i=2;i<=n;i++)
        {
            int  st=1,ed=n-i+1;type^=1;
            if(type==0)ed=id-1;
            else  st=id;
            for(int  j=st;j<=ed;j++)
            {
                if(f[i][j][type]<m)m-=f[i][j][type];
                else{b[i]=findkth(j);id=j;break;}
            }
        }
        for(int  i=1;i<n;i++)printf("%d ",b[i]);
        printf("%d\n",b[n]);
    }
    return  0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章