CodeForces 501D – Misha and Permutations Summation(康托/逆康托展开+树状数组+二分)

题意:给两个排列,分别算出是第几小的排列,这两个数字求和以后再模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个比它小的数字的数是几。这一步使用二分解决。
#include<iostream>
#include<vector>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int maxn=200005;
int n;
struct BIT
{
    int dat[maxn];
    void clear()
    {
        memset(dat,0,sizeof(dat));
    }
    int lowbit(int x)
    {
        return -x&x;
    }
    int getSum(int x)
    {
        int sum=0;
        while(x>0)
        {
            sum+=dat[x];
            x-=lowbit(x);
        }
        return sum;
    }
    void add(int x,int val)
    {
        while(x<=n)
        {
            dat[x]+=val;
            x+=lowbit(x);
        }
    }
};

int a[maxn],b[maxn];
int v[maxn];
BIT bit;
void kuangtuo(int *arr,BIT &bit)
{
    bit.clear();
    for(int i=1; i<=n; ++i)
    {
        int s=arr[i]-1-bit.getSum(arr[i]-1);
        bit.add(arr[i],1);
        v[i]+=s;
    }
}
int c[maxn];
int Bsearch(int low,int high,int K)
{
    int mid=low+(high-low)/2;
    while(low<high)
    {
        if((mid-bit.getSum(mid))>=K) high=mid;
        else low=mid+1;
        mid=low+(high-low)/2;
    }
    return mid;
}
void nikuangtuo(int *arr,BIT &bit)
{
    bit.clear();
    for(int i=1; i<=n; ++i)
    {
        int p=Bsearch(1,n,(v[i]+1));
        arr[i]=p;
        bit.add(p,1);
    }
}
int main()
{
    scanf("%d",&n);
    for(int i=1; i<=n; ++i)
    {
        int x;
        scanf("%d",&x);
        a[i]=x+1;
    }
    for(int i=1; i<=n; ++i)
    {
        int x;
        scanf("%d",&x);
        b[i]=x+1;
    }
    kuangtuo(a,bit);
    kuangtuo(b,bit);
    for(int i=n; i>=1; --i)
    {
        int j=n-i+1;
        v[i-1]+=v[i]/j;
        v[i]=v[i]%j;
    }
    nikuangtuo(c,bit);
    for(int i=1; i<=n; ++i)
    {
        if(i!=1) printf(" ");
        printf("%d",c[i]-1);
    }
    printf("\n");
    return 0;
}



你可能感兴趣的:(树状数组,二分,康托展开)