题意:设1 ~ n的一个排列为A1,A2,A3 ...... An,将其表示成循环节的形式可以构造一个新排列。例如:n = 5时,对于排列(1,3,5,2,4),它表示成循环节的形式为(1)(3542)但是规定循环节长度大于等于1的情况下必须以最大的数字开头写,即应表示成(1)(5423),那么构造出的新排列为(1,5,4,2,3)。如果构造出的新排列与原排列相同,则称这样的排列是好排列。例如排列(1,3,2,4)按上述规定表示成循环节形式为(1)(32)(4),构造出的新排列仍为(1,3,2,4)。所以(1,3,2,4)是n = 4时的一个好排列。现在给定n和k,求1 ~ n的按字典序递增的第k个好排列。
看似很复杂,但是在纸上写几个好排列你就发现问题了:
随便写几个,比如(1,3,2,4),(2,1,4,3),(1,2,4,3,5,6,8,7)等。你会发现排列里每个位置 i 要么满足A[i] = i,要么满足A[i] = i + 1且A[i + 1](A[i - 1]) = i。下面我们证明它。
我们假设原排列为A,新排列为B。因为B本来是A的循环节表示,因此我们在B中任取一个长度大于1的循环节作分析:
假设这个循环节的长度大于2,设这个循环节为(i,j,k......)于是有A[i] = j,A[j] = k,即数 j 所在的位置为 i,数 k 所在的位置为 j。又由于i,j,k三个数是连续位置的,所以必有 j = i + 1。但是由于题目规定循环节必须满足从最大的数写起。所以又有i > j,因此i + 1 = j不成立,得出矛盾。所以所有的循环节长度只能是1或2。下面进一步证明长度等于2的循环节里的两个数必须相邻。
假设B中某个循环节为(i,j),则有A[i] = j,A[j] = i。即数 i 的位置为 j,数 j 的位置为 i。但是注意B排列里 i 和 j 的位置是连续的,因此必有i = j + 1。而这样是可以满足 i > j 的,并不矛盾。
这样,这题找出了规律,接下来就是怎么求第k个排列的问题了。当然,直观上我们觉得如果第k个排列里从左往右看第一次出现交换的两个数为 i 和 i + 1,那么也就是在 i 和 i + 1保持原位的情况下后面的数所有的排列情况的总和是小于k的。于是我们可以先求出长度为n的好排列的个数。设为cnt[n]个。那么很容易用递推的思想求出来:n个数的好排列,如果1不和2换,那么有cnt[n - 1]种好排列。如果1和2换,那么剩下n - 2个数,有cnt[n - 2]种好排列。于是cnt[n] = cnt[n - 1] + cnt[n - 2]。即Fib数列。注意初始项为Fib[1] = 1,Fib[2] = 2。但是这里我们取用Fib[0] = Fib[1] = 1作为前两项,因为Fib[0]在边界计算中会用到。
这样求第k排列就很简单了,从1到n枚举。对当前的i,如果k > Fib[n - i](即剩下的数所有的排列情况总数小于k)那么此 i 和 i + 1是必须交换的。交换后k -= Fib[n - i],再往下继续枚举。否则 i 不需要变。这样直到k变为0,得出答案。
#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <string>
#include <algorithm>
#include <stack>
#include <queue>
#include <vector>
#include <map>
#include <set>
using namespace std;
const int MAX = 100;
long long Fib[MAX];
int num;
void initial()
{
Fib[0] = Fib[1] = 1;
for(int i = 2; i < MAX; i++)
{
Fib[i] = Fib[i - 1] + Fib[i - 2];
if(Fib[i] > 1e18)
{
num = i;
break;
}
}
}
int main()
{
initial();
int n;
long long k;
while(scanf("%d%I64d", &n, &k) != EOF)
{
bool start = true;
for(int i = 1; i <= n; i++)
{
if(start)
start = false;
else
printf(" ");
if(k > Fib[n - i])
{
printf("%d %d", i + 1, i);
k -= Fib[n - i];
i++;
}
else
printf("%d", i);
}
printf("\n");
}
return 0;
}