解决此类问题常见思路:
为求以 A1, A2, ... , AK 为前K项的所有栏杆字典排序第K小的序列.
这里给出其他牛人的介绍
先抛开此题,用最简单的全排列问题来说明这种方法。如1,2,3,4的全排列,共有4!种,求第10个的排列是? 先试首位是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。(从1计起) 这种方法的核心在于求,给定前缀下后面总的排列数。全排列问题较易求。同时还需记录前面用过的数字。
这里注意到, '高低起伏', 是相邻两个来决定的.
所以我们状态开设为 G( n, h ) , 且有两种不同状态 up, down
所以状态为
G( n, h ).up 意义为 n个数,第一个以高度h开始,第一第二个呈上升的排列方案数
G( n, h ).down 意义为 n个数,第一个以高度h开始,第一第二个呈下降的排列方案数
注意到题目要求 '高低起伏'
所以 ( i, i-1 ) 为上升(或下降) 则 ( i-1, i-2 ) 必定为 下降(或上升)
所以 我们可以得出递推关系式:
注意,
假设从 1...N中除去 A1,A2,...,Ak 项后剩余 B1,B2,..,B(n-k) ,
那么方案数目则转换成了求, B1,B2,..,B(n-k)项 头两块木头 上升(或下降)的 栅栏数目.
又因为所谓的'高低起伏'只是木头与木头的相对高度,而非绝对高度.
所以对于 N个高度,我们去掉了其中的T后, 剩下N-1不同数字可以映射到 1到n-1.相对为1,2,..,n-1的全排列
所以方案中的T,为1,2,...,n中的相对第T小.
这样做,我们在N变换的过程中可以重复利用前面计算好的值,而节省大量时间.
还要注意.求A1时,因为字典序是down小,up大, 当down不满足时,也要减去其方案数.
解题代码:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<cstdio> #include<cstring> #include<cstdlib> #include<map> #include<vector> #include<algorithm> #include<iostream> using namespace std; typedef long long LL; struct node{ LL up, down; }G[25][25]; int p[25], n; LL C, N; bool vis[25]; map< int,int > Mp; // 递归函数获取 后面K个,以高度H为开始的序列方案数 void Getg() { memset(G,0,sizeof(G)); for(int i = 1; i <= 1; i++) G[1][i].down = G[1][i].up = 1; for(int i = 2; i <= 20; i++) { for(int T = 1; T <= i; T++) { for(int j = 1; j <= T-1; j++) // 长度I位置的T已经被去掉了 G[i][T].down += G[i-1][j].up; for(int j = T; j <= i-1; j++) // 原本的T已经去掉,T+1映射成相对位置T,后面只有i-1个数 G[i][T].up += G[i-1][j].down; } } } int main() { Getg(); int T; scanf("%d", &T); while( T-- ) { scanf("%d%lld", &n, &C); N = C; memset( vis, 0, sizeof(vis) ); memset( p, 0, sizeof(p) ); int clas; // 单独处理 A1 位置 for(int h = 1; h <= n; h++) { vis[h] = true; p[1] = h; LL tmp = G[n][h].down+G[n][h].up; // printf("down=%lld,up=%lld\n",G[n][h].down, G[n][h].up); if( tmp >= N ){ if( G[n][h].down >= N ) clas = 0; else{ N -= G[n][h].down; clas = 1; } break; } else N -= tmp; vis[h] = false; } // printf("p[1] = %d, class = %d, N = %lld\n", p[1], clas, N ); // 处理 Ai 位置 for(int i = 2; i <= n; i++) { int cnt = 1; Mp.clear(); for(int h = 1; h <= n; h++) if( !vis[h] ) Mp[h] = cnt++; if( clas == 0 ) // (i-1,i) 为down, 则 (i,i+1)必须为 up {// G[i-1]位置为 down //枚举 Ai for(int h = 1; h < p[i-1]; h++) if( !vis[h] ) //未被使用 { int T = Mp[h]; //相对高度第T小 vis[h] = true; p[i] = h; LL tmp = G[n+1-i][T].up; if( tmp >= N ) { clas = 1; break; } else N -= tmp; vis[h] = false; } } else // (i-1,i) 为up, 则 (i,i+1) 必须为 down {// G[i-1]位置为 up for(int h = p[i-1]+1; h <= n; h++) if( !vis[h] ) { int T = Mp[h]; vis[h] = true; p[i] = h; LL tmp = G[n+1-i][T].down; // printf("tmp = %lld\n", tmp ); if( tmp >= N ) { clas = 0; break; } else N -= tmp; vis[h] = false; } } // printf("p[%d] = %d, class = %d, N = %lld\n",i, p[i], clas, N ); } for(int i = 1; i <= n; i++ ) printf( i == 1 ? "%d" : " %d", p[i] ); puts(""); } return 0; }