一.题目链接:
POJ-1037
二.题目大意:
输入两个整数 n,c
要求输出字典序为 c 的 1~n 的**排列.
**排列是指长度为 n,每个数字为 1~n 且数字不重复且大小高低交错的排列.
三.分析:
可以用 “试填法” 来确定排名为 c 的排列.
具体来说,我们可以枚举第一个数的大小
当第一个数为 a[1] 时,设后面 n - 1 个数构成的**排列方案数为 t
若 t >= c,则说明第一个数为 a[1]
否则,说明第一个数应该更大,此时令 c - t,a[1] 增加 1.
重复上述过程即可确定第一个数的大小,同理确定所有数的大小.
所以我们应先预处理出 t.
f[i][j][k] 表示 i 个互异的数,其中最左边的数从小到大排序在这 i 个数中排名为 j,并且最左边的数为低(0)/高(1)位,时的方案数.
状态转移方程很简单,详见代码.
PS:在确定第一个数的大小时,应先枚举 f[n][j][1],再枚举f[n][j][1],这是因为当两者同时满足条件时,很明显 1 为高位的字典序更小.
四.代码实现:
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
const int M = (int)20;
bool vis[M + 5];
ll f[M + 5][M + 5][2];
void init()
{
f[1][1][0] = f[1][1][1] = 1;
for(int i = 2; i <= M; ++i)
{
for(int j = 1; j <= i; ++j)
{
for(int k = j; k <= i - 1; ++k) f[i][j][0] += f[i - 1][k][1];
for(int k = 1; k <= j - 1; ++k) f[i][j][1] += f[i - 1][k][0];
}
}
}
int main()
{
init();
int T;
scanf("%d", &T);
while(T--)
{
memset(vis, 0, sizeof(vis));
int n; ll c;
scanf("%d %lld", &n, &c);
int ls, k;
for(int j = 1; j <= n; ++j)
{
if(f[n][j][1] >= c)
{
ls = j;
k = 1;
break;
}
else c -= f[n][j][1];
if(f[n][j][0] >= c)
{
ls = j;
k = 0;
break;
}
else c -= f[n][j][0];
}
vis[ls] = 1;
printf("%d", ls);
for(int i = 2; i <= n; ++i)
{
k ^= 1;
int j = 0;
for(int l = 1; l <= n; ++l)
{
if(vis[l]) continue;
++j;
if(k == 0 && ls > l || k == 1 && ls < l)
{
if(f[n - i + 1][j][k] >= c)
{
ls = l;
break;
}
else c -= f[n - i + 1][j][k];
}
}
vis[ls] = 1;
printf(" %d", ls);
}
printf("\n");
}
return 0;
}