poj1037(dP+排列计数)

来源:互联网 发布:数据录入员工作的细节 编辑:程序博客网 时间:2024/06/16 14:46

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;}


0 0