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