题意:N 个木棒, 长度分别为1, 2, ..., N。构成美妙的栅栏。要求满足:除了两端的木棒外,每一跟木棒,要么比它左右的两根都长,要么比它左右的两根都短。即木棒呈现波浪状分布,这一根比上一根长了,那下一根就比这一根短,或反过来。符合上述条件的栅栏建法有很多种,对于满足条件的所有栅栏, 按照字典序(从左到右, 从低到高) 排序。问给定一个栅栏的排序号,请输出对应的栅栏方案, 即每一个木棒的长度。
思路(参考郭炜老师课件):用dp确定数量,输出采用排列计数的思想。
dp[i][k][DOWN]是i根木棒中以第k短的木棒打头的DOWN方案数。然后对C进行动归:dp[i][k][UP] = ∑ dp[i-1][M][DOWN],M = k ... i -1。dp[i][k][DOWN] = ∑ dp[i-1][N][UP],N = 1... k-1。
初始条件:dp[1][1][UP]=dp[1][1][DOWN] = 1。
接下来考虑计数问题:本题待求方案的序号为C
先假设第1短的木棒作为第一根,看此时的方案数 P(1)是否>=C,如果否,则应该用第二短的作为第一根,C减去P(1) ,再看此时方案数P(2)和C比如何。如果还 < C ,则应以第三短的作为第一根,C再减去P(2) ....若发现第i短的作为第一根时,方案数已经不小于C,则确定应该以第i短的作为第一根, C减去第i短的作为第一根的所有方案数,然后再去确定第二根。
下一根的长度与上一根的长度有关,可以用上一根取的是第几小来表示,而且当前是up方案还是down方案也与上一个方案种类相关。前者通过循环起始和终止位置控制,后者通过两个变量控制。具体见代码。
#include <cstdio> #include <cstring> #include <algorithm> #include <cmath> #define N 25 #define INF 0x3fffffff using namespace std; int output[N]; long long dp[N][N][2],m; int used[N]; int n,T; void solve(){ int i,j,k; memset(dp, 0, sizeof(dp)); dp[1][1][0] = dp[1][1][1] = 1; for(i = 2;i<=n;i++){ for(j = 1;j<=i;j++){ for(k = 1;k<=j-1;k++) dp[i][j][1] += dp[i-1][k][0]; for(k = j;k<=i-1;k++) dp[i][j][0] += dp[i-1][k][1]; } } } int idm(int i){//输出当前可用数字中的第i小数 int j,sum; for(j = 1,sum=0;j<=n;j++){ if(!used[j]) sum++; if(sum == i) return j; } return -1; } void print(long long y){ int i,j,k,x,begin,end,up,down; memset(used, 0, sizeof(used)); end = x = n; begin = 1; up = down = 1;//第一个位置down方案或者up方案皆可 for(i = 1;i<=n;i++){ for(j = begin;j<=end;j++){ if(down && y <= dp[x][j][1]){//如果采用的是第j小数开头的down方案 k = idm(j);//取得第j小数 output[i] = k; used[k] = 1; begin = 1;//那么下一个数只能是up方案,而且起始数字只能是从第1小到第i-1小(即比这个位置选出的数字小) end = j-1; down = 0;//表示下一个数只能是up方案 up = 1; break; } if(down && y>dp[x][j][1])//注意逻辑清晰 y -= dp[x][j][1]; if(up && y<= dp[x][j][0]){ k = idm(j); output[i] = k; used[k] = 1; begin = j;//与上面同理,下一个只能是down方案,而且只能是以第j小到第x-1小打头 end = x-1; up = 0; down = 1; break; } if(up && y>dp[x][j][0]) y -= dp[x][j][0]; } x--; } for(i = 1;i<n;i++) printf("%d ",output[i]); printf("%d\n",output[n]); } int main(){ scanf("%d",&T); while(T--){ scanf("%d %lld",&n,&m); solve(); print(m); } return 0; }