計數類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
題解
,分別表示的是點集在~,第一個位置選的是且第一個位置是的時候的方案數。
那麼很明顯的一個狀態轉移方程是:
意思就是把點集中的~加,把插到原本的位置上,就成了的點集了。
那麼很顯然:
然後我們可以對於第位的數字,通過看排名範圍來確定第一位是什麼數字,還是,然後後面也一樣慢慢逼近就行了。
#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;
}