poj1037(dP+排列计数)

http://poj.org/problem?id=1037

例八: POJ 1037 一个美妙的栅栏
 N 个木棒, 长度分别为1, 2, …, N.
 构成美妙的栅栏
 除了两端的木棒外,每一跟木棒,要么比它左右的两根都长,要
么比它左右的两根都短。
 即木棒呈现波浪状分布,这一根比上一根长了,那下一根就比这
一根短,或反过来


 问题: 符合上述条件的栅栏建法有很多种,对
于满足条件的所有栅栏, 按照字典序(从左到
右, 从低到高) 排序。
 给定一个栅栏的排序号,请输出该栅栏, 即每
一个木棒的长度.
例题: POJ 1037 一个美妙的栅栏

 输入数据
 第一行是测试数据的组数 K (1 <= K <= 100)。接下来的K行,
每一行描述一组输入数据.
 每一组输入数据包括两个整数 N 和 C. N ( 1 <= N <= 20) 表示
栅栏的木棒数, C表示要找的栅栏的排列号.
 输出数据
 输出第C个栅栏, 即每一个木棒的长度
 设20个木棒可组成的栅栏数是T; 我们假设 T
可以用64-bit长整数表示,1 < C <= T
例题: POJ 1037 一个美妙的栅栏
 输入样例
 输出样例
2
2 1
3 3
1 2
2 3 1
80
解题思路
 问题抽象:给定1到N 这N个数字,将这些数字高低交替进
行排列 ,把所有符合情况的进行一个字典序排列,问第C个
排列是一个怎样的排列
 总体思想
 动归 + 排列计数
 动归
动归解题思路
 1) 设 A[i] 为i根木棒所组成的合法方案数目。看看能否找出A[i]和A[i-1]
或A[i-j]之间的递推关系(所有木棒总数是i)。称i根木棒的合法方案集合
为S(i)
 2) 在选定了某根木棒x作为第一根木棒的情况下,剩下i-1根木棒的合法
方案数是A[i-1]。但是,这A[i-1]种方案,并不是每种都能和x形成新的
合法方案。将第一根比第二根长的方案称为DOWN方案,第一根比第
二根短的称为UP方案,则,S(i-1)中,第一根木棒比x长的DOWN方
案,以及第一根木棒比x短的UP方案,才能和x构成S(i)中的方案。
动归解题思路
 3) 置A[i] = 0。先枚举x。然后针对每个x,枚举x后面的那根木棒y。如果 y >
x(x<y的情况类推),则:
A[i] += 以y打头的DOWN方案数
但以y打头的DOWN方案数,又和y的长短有关。
于是难以直接从 A[i-1]或 A[i-j]推出 A[i]
 4) 考虑将A[i]这种粗略的状态描述方式细化,即加上限制条件后分类。设
A[i] = ∑ B[i][k] k = 1….i
B[i][k] 是S(i)中以第k短的木棒打头的方案数。尝试对 B 进行动归。第k短
,指的是i根木棒中第k短。
动归解题思路

5) B[i][k] = ∑ B[i-1][M](DOWN)+ ∑ B[i-1][N](UP)
M = k ... i-1 , N = 1… k-1
还是没法直接推。于是把B再分类细化:
B[i][k] = C[i][k][DOWN] + C[i][k][UP]
C[i][k][DOWN] 是S(i)中以第k短的木棒打头的DOWN方案数。然后试图对C进行动

C[i][k][UP] = ∑ C[i-1][M][DOWN]
M = k ... i -1
C[i][k][DOWN] = ∑ C[i-1][N][UP]
N = 1… k-1
初始条件:C[1][1][UP]=C[1][1][DOWN] = 1
动归解题思路
 经验:当选取的状态,难以进行递推时(分解出的子问题和原
问题形式不一样,或不具有无后效性),考虑将状态增加限制
条件后分类细化,即增加维度,然后在新的状态上尝试递推
排序计数
 如1,2,3,4的全排列,共有4!种,求第10个的排列是(从1计
起)?
 先试首位是1,后234有3!=6种<10,说明首位1偏小,问题转换成
求2开头的第(10-6=4)个排列,而3!=6 >= 4,说明首位恰是2。
 第二位先试1(1没用过),后面2!=2个<4,1偏小,换成3(2用过
了)为第二位,待求序号也再减去2!,剩下2了。而此时2!>=2,
说明第二位恰好是3。
 第三位先试1,但后面1!<2,因此改用4。末位则是1了。
 这样得出,第10个排列是2-3-4-1。
排序计数
本题待求方案的序号为C
本题就是先假设第1短的木棒作为第一根,看此时的方案数
P(1)是否>=C,如果否,则应该用第二短的作为第一根,C 减去P(1)
,再看此时方案数P(2)和C比如何。如果还 < C ,则应以第三短的
作为第一根,C再减去P(2) ….
若发现 第 i短的作为第一根时,方案数已经不小于C,则确定
应该以第i短的作为第一根, C减去第 i短的作为第一根的所有方案
数,然后再去确定第二根….
微调:以第i短的木棒作第k根时,有UP和DOWN两类方案,
先用DOWN的方案数和C比较

//POJ1037 A decorative fence by Guo Wei
#include <iostream>
#include <algorithm>
#include <cstring>
#include<cstdio>

using namespace std;
const int UP =0;
const int DOWN =1;
const int MAXN = 25;
long long C[MAXN][MAXN][2]; //C[i][k][DOWN] 是S(i)中以第k短的木棒打头的DOWN方案数,C[i][k][UP] 是S(i)中以第k短的木棒打头的UP方案数,第k短指i根中第k短
void Init(int n)
{
    memset(C,0,sizeof(C));
    C[1][1][UP] = C[1][1][DOWN] = 1;
    for( int i = 2 ; i <= n; ++ i )
        for( int k = 1; k <= i; ++ k )   //枚举第一根木棒的长度
        {
            for( int M = k; M <i ; ++M ) //枚举第二根木棒的长度
                C[i][k][UP] += C[i-1][M][DOWN];
            for( int N = 1; N <= k-1; ++N ) //枚举第二根木棒的长度
                C[i][k][DOWN] += C[i-1][N][UP];
        }
//总方案数是 Sum{ C[n][k][DOWN] + C[n][k][UP] } k = 1.. n;
}

void Print(int n, long long cc)
{
    long long skipped = 0; //已经跳过的方案数
    int seq[MAXN]; //最终要输出的答案
    int used[MAXN]; //木棒是否用过
    memset(used,0,sizeof(used));
    for( int i = 1; i<= n; ++ i )   //依次确定每一个位置i的木棒序号
    {
        long long oldVal = skipped;
        int k;
        int No = 0; //k是剩下的木棒里的第No短的,No从1开始算
        for( k = 1; k <= n; ++k )   //枚举位置i的木棒 ,其长度为k
        {
            oldVal = skipped;
            if( !used[k])
            {
                ++ No; //k是剩下的木棒里的第No短的
                if( i == 1 )
                    skipped += C[n][No][UP] + C[n][No][DOWN];
                else
                {
                    if( k > seq[i-1] && ( i <=2 || seq[i-2]>seq[i-1]))//合法放置
                        skipped += C[n-i+1][No][DOWN];
                    else if( k < seq[i-1] &&(i<=2 || seq[i-2]<seq[i-1])) //合法放置
                        skipped += C[n-i+1][No][UP];
                }
                if( skipped >= cc )
                    break;
            }
        }
        used[k] = true;
        seq[i] = k;
        skipped = oldVal;
    }
    for( int i = 1; i <= n; ++i )
        if( i < n) printf("%d ",seq[i]);
        else
            printf("%d",seq[i]);
    printf("\n");
}
int main()
{
    int T,n;
    long long c;
    Init(20);
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d %lld",&n,&c);
        Print(n,c);
    }
    return 0;
}


  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值