poj 1037 dp+排列计数(美妙的栅栏)

题意: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;
}


你可能感兴趣的:(poj 1037 dp+排列计数(美妙的栅栏))