1529 排列与编码
给出两个长度为N的排列P1,P2,有着完全相同的元素。设这些元素所能组成的不同的排列数量共L个(L可能很大),按照字典序排序后,编码为 0 到 L - 1。其中P1的编码为R1,P2的编码为R2。求编码为:(R1 + R2) % L的排列是什么?
例如:P1 = {2, 2, 1},P2 = {2, 1, 2},则L = 3, R1 = 2, R2 = 1,那么需要求的排列编码为:(R1 + R2) % L = (2 + 1) % 3 = 0,对应的排列为:{1, 2, 2}。
输入
第1行:1个数N,表示排列的长度(1 <= N <= 50000)。
第2 -N + 1行:每行2个数,用空格分隔,对应P1[i]和P2[i](1 <=P[i] <= 10^9)。
(输入数据保证P1同P2有着完全相同的元素)。
输出
输出共n行,对应题目要求的排列。
输入样例
3
2 2
2 1
1 2
输出样例
1
2
2
分析:
对于这个问题,我并没有想出优雅的方法,而是比较暴力的使用高精计算,因此你最好选择Java 8,Python,Ruby,Go等支持FFT高精的语言来解题(计算中需要用到高精除法)。
假如没有重复的话,直接使用康托展开(康托展开_百度百科)便可以处理,有重复的话需要将计算结果除以当前数字出现的次数,考虑到大数运算的复杂度,整个算法是平方的。所以我们考虑用混合进制来表示大数,但每一位都需要用高精来存储,以便好好利用FFT的优势。
混合进制的可选方案也很多,在某一位上甚至可以使用5/2这类进制(方便处理重复字符的情况),但考虑整个计算除了要计算编码,还要根据编码反向计算排列,按照这种方式,很难做到一致,所以需要考虑无论是正向还是反向,都可以处理的一种进制,用阶乘进制来表示。
阶乘进制的第n位表示(n - 1)!,假设这一位的数字为k,则表示k*(n - 1)!。
第一步:使用康托展开,利用线段树或树状数组处理前缀和,可以求出某个排列的混合进制表示。
例如:1 3 1 2 2 1,处理结果为:
0 4 0 1 0 0 (后面的数字中有多少个比当前数字小)
3 1 2 2 1 1 (当前数字共有多少个,1有3个,所以第一位是3,但处理到第3位,第2个1时,由于前面的1已经减去,只剩下2个,所以第3位是2)
有了这两个计数,就好计算编码了,最终的结果是:
0 * (1 ) * (1 * 2 * 2 * 1 * 3) +
0 * (1) * ( 2 * 2 * 1 * 3) +
1 * (1 * 2) * ( 2 * 1 * 3) +
0 * (1 * 2 * 3) * (1 * 3) +
4 * (1 * 2 * 3 * 4) * (3) +
0 * (1 * 2 * 3 * 4 * 5)
= 312
这里计算的实际上是真实R的12倍,312 = 12 * 26,因为采用这种阶乘进制,会让结果扩大(1 * 1 * 2 * 2 * 1 * 3) = 12倍。
当然,按照上面的方法来计算,考虑高精本身的复杂度,算法还是平方级的,因此我们需要进行分治,整个过程相当于把这样一个混合进制的大数,转为普通进制的大数。而进制转换的复杂度是n*log(n)^2的。
0 * (1 ) * (1 * 2 * 2 * 1 * 3) +
0 * (1) * ( 2 * 2 * 1 * 3) +
1 * (1 * 2) * ( 2 * 1 * 3) +
0 * (1 * 2 * 3) * (1 * 3) +
4 * (1 * 2 * 3 * 4) * (3) +
0 * (1 * 2 * 3 * 4 * 5)
变为:
(0 * (1 ) * (1 * 2) +
0 * (1) * ( 2) +
1 * (1 * 2) )
* ( 2 * 1 * 3) +
(1 * 2 * 3) *
(0 * (1 * 3) +
4 * (4) * (3) +
0 * (4 * 5))
然后递归处理。这样计算可以让复杂度变为nlogn^2(因为大数运算的两边数值大小相近,FFT的效果最好)。
放Java代码:
import java.math.BigInteger;
import java.io.*;
import java.util.*;
public class Program
{
public static void main(String[] args) throws IOException
{
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in), 1 << 12);
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(System.out), 1 << 12);
int n = Integer.parseInt(reader.readLine());
int[] n1 = new int[n];
int[] n2 = new int[n];
DataServer data = new DataServer();
data.Init(n);
for(int i = 0; i < n; i++)
{
String[] input = reader.readLine().spl