Codeforces 553B Kyoya and Permutation 排列问题

题意:设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;
}


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值