Codeforces 553B Kyoya and Permutation 排列问题

来源:互联网 发布:淘宝搜索引擎怎么做 编辑:程序博客网 时间:2024/06/09 16:58

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


0 0
原创粉丝点击