51nod1529 排列与编码

本文介绍了一种解决排列编码问题的方法,通过康托展开和阶乘进制来计算排列编码。当存在重复元素时,使用混合进制表示大数,并利用FFT进行高精度计算。算法通过分治策略将复杂度降低到nlogn^2。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值