题意:给两个排列,分别算出是第几小的排列,这两个数字求和以后再模n!得到一个数,输出这个数对应的排列。
思路:很明显的康托/逆康托展开。难点在于如何快速求解康托/逆康托以及模n!上。
在康托展开中,遍历每一位是在所难免的,时间复杂度是O(n),在统计比a[i]小的数字个数的时候显然不能遍历了,可以用树状数组加速,时间复杂度是O(lgn)。这里并不得到的数字加起来,因为可能达到n!,太大了,而是按i!,这样每一位的保存。这一步的总复杂度是O(nlgn)。
对a、b两个排列都进行康托展开,得到一个数组v,这是它们每一位i!的和。
这时候需要进行模n!。
数组v的组成是这样的v[n]*n!+v[n-1]*(n-1)!+…+v[1]*1!+v[0]*0!
可以发现如果v[0]大于1,那么它就可以进位给v[1]了,如果v[1]大于2那么它就可以进位给v[2]了。即v[i+1]+=v[i]/(i+1),v[i]=v[i]%(i+1)。这样就完成了模n!。
最后一步是逆康托展开。
显然数组v的每个数v[i]就是它模i!的商。所以问题就在于如何快速找到有k个比它小的数字的数是几。这一步使用二分解决。
思路:很明显的康托/逆康托展开。难点在于如何快速求解康托/逆康托以及模n!上。
在康托展开中,遍历每一位是在所难免的,时间复杂度是O(n),在统计比a[i]小的数字个数的时候显然不能遍历了,可以用树状数组加速,时间复杂度是O(lgn)。这里并不得到的数字加起来,因为可能达到n!,太大了,而是按i!,这样每一位的保存。这一步的总复杂度是O(nlgn)。
对a、b两个排列都进行康托展开,得到一个数组v,这是它们每一位i!的和。
这时候需要进行模n!。
数组v的组成是这样的v[n]*n!+v[n-1]*(n-1)!+…+v[1]*1!+v[0]*0!
可以发现如果v[0]大于1,那么它就可以进位给v[1]了,如果v[1]大于2那么它就可以进位给v[2]了。即v[i+1]+=v[i]/(i+1),v[i]=v[i]%(i+1)。这样就完成了模n!。
最后一步是逆康托展开。
显然数组v的每个数v[i]就是它模i!的商。所以问题就在于如何快速找到有k个比它小的数字的数是几。这一步使用二分解决。
#include<iostream>
#include<vector>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const