codeforces 501D Misha and Permutations Summation(康拓展开+数据结构)

题目链接

Misha and Permutations Summation
time limit per test
2 seconds
memory limit per test
256 megabytes
input
standard input
output
standard output

Let's define the sum of two permutations p and q of numbers 0, 1, ..., (n - 1) as permutation , where Perm(x) is the x-th lexicographically permutation of numbers 0, 1, ..., (n - 1) (counting from zero), and Ord(p) is the number of permutation p in the lexicographical order.

For example, Perm(0) = (0, 1, ..., n - 2, n - 1)Perm(n! - 1) = (n - 1, n - 2, ..., 1, 0)

Misha has two permutations, p and q. Your task is to find their sum.

Permutation a = (a0, a1, ..., an - 1) is called to be lexicographically smaller than permutation b = (b0, b1, ..., bn - 1), if for some kfollowing conditions hold: a0 = b0, a1 = b1, ..., ak - 1 = bk - 1, ak < bk.

Input

The first line contains an integer n (1 ≤ n ≤ 200 000).

The second line contains n distinct integers from 0 to n - 1, separated by a space, forming permutation p.

The third line contains n distinct integers from 0 to n - 1, separated by spaces, forming permutation q.

Output

Print n distinct integers from 0 to n - 1, forming the sum of the given permutations. Separate the numbers by spaces.

Sample test(s)
input
2
0 1
0 1
output
0 1
input
2
0 1
1 0
output
1 0
input
3
1 2 0
2 1 0
output
1 0 2
Note

Permutations of numbers from 0 to 1 in the lexicographical order: (0, 1), (1, 0).

In the first sample Ord(p) = 0 and Ord(q) = 0, so the answer is .

In the second sample Ord(p) = 0 and Ord(q) = 1, so the answer is .

Permutations of numbers from 0 to 2 in the lexicographical order: (0, 1, 2), (0, 2, 1), (1, 0, 2), (1, 2, 0), (2, 0, 1), (2, 1, 0).

In the third sample Ord(p) = 3 and Ord(q) = 5, so the answer is .


题意:已知两个0到n-1的n个数的排列a,b。ord(a)在0到n-1的数的所有排列中的按字典序排序后的位置。ord(b)同理。输出排列x,ord(x)=(ord(a)+ord(b))%n!。

题解:首先要了解康拓展开:康拓展开

x=a[n]*(n-1)!+a[n-1]*(n-2)!+.......a[1]*0!。,0<=a[i]<i。

由于数据范围太大,直接求出ord显然是不行的。我们可以根据a,b的康拓展开式,直接求出x的康拓展开式。

因为a[i]<=i-1。

所以,排列a康拓展开有可能的最大值为:(n-1)*(n-1)!+(n-2)*(n-2)!+.....(i-1)*(i-1)!+...0*0!

同理排列b康拓展开的最大值为:(n-1)*(n-1)!+(n-2)*(n-2)!+.....(i-1)*(i-1)!+...0*0!

      将两个排列的康拓展开相加可得:n!+(n-1)*(n-1)!+(n-2)*(n-2)!+....(i-1)*(i-1)!+....0*0!

       上式%n!,则可得:(n-1)*(n-1)!+(n-2)*(n-2)!+.........(i-1)*(i-1)!+....0*0!

由此可见,两个排列的康拓展开之和,最大到达n!的数量级,对n!取模过后,剩下的就是一个排列的康拓展开。我们把这个康拓展开逆展开,就解决问题了。

我们可以用类似高精度加法的方法,求出两个排列的康拓展开的和%n!的康拓展开。然后用树状数组+二分的方法,将康拓展开逆展开。复杂度为O(n*lgn*lgn)。当然也可以用平衡树或者线段树,不用二分,复杂度为O(n*lgn)。

我的代码是树状数组+二分的方法,代码如下:

#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<string.h>
#include<string>
#include<queue>
#include<stack>
#include<map>
#include<set>
#include<stdlib.h>
#include<vector>
#define inff 0x3fffffff
#define nn 210000
#define mod 1000000007
typedef long long LL;
const LL inf64=inff*(LL)inff;
using namespace std;
int n;
int a[nn],b[nn];
int ans[nn];
int c[nn];
int dp[nn];
int lowbit(int x)
{
    return x&(-x);
}
void modify(int x,int y)
{
    for(int i=x;i<=n;i+=lowbit(i))
    {
        c[i]+=y;
    }
}
int getsum(int x)
{
    int sum=0;
    for(int i=x;i>=1;i-=lowbit(i))
        sum+=c[i];
    return sum;
}
int main()
{
    int i;
    while(scanf("%d",&n)!=EOF)
    {
        for(i=1;i<=n;i++)
        {
            scanf("%d",&a[i]);
        }
        for(i=1;i<=n;i++)
        {
            scanf("%d",&b[i]);
        }
        int ix;
        memset(c,0,sizeof(c));
        for(i=1;i<=n;i++)
        {
            ix=getsum(a[i]);
            modify(a[i]+1,1);
            a[i]=a[i]-ix;
        }
        memset(c,0,sizeof(c));
        for(i=1;i<=n;i++)
        {
            ix=getsum(b[i]);
            modify(b[i]+1,1);
            b[i]=b[i]-ix;
        }
        ix=0;
        for(i=n;i>=1;i--)
        {
            ans[i]=(a[i]+b[i]+ix)%(n-i+1);
            ix=(a[i]+b[i]+ix)/(n-i+1);
        }
        for(i=1;i<=n+1;i++)
            dp[i]=i;
        memset(c,1,sizeof(1));
        int l,r,mid;
        for(i=1;i<=n;i++)
        {
            l=1,r=n;
            while(l<r)
            {
                mid=(l+r)/2;
                if(mid-getsum(mid)<ans[i]+1)
                    l=mid+1;
                else
                    r=mid;
            }
            ans[i]=l;
            modify(l,1);
        }
        for(i=1;i<=n;i++)
        {
            printf("%d%c",ans[i]-1,i==n?'\n':' ');
        }
    }
    return 0;
}

线段树做法代码如下:

#include<algorithm>
#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#include<math.h>
#define nn 210000
#define eps 1e-8
#define inff 0x7fffffff
#define lson rt<<1,l,m
#define rson rt<<1|1,m+1,r
#define mod 20071027
using namespace std;
typedef __int64 LL;
typedef unsigned long long LLU;
int n;
int a[nn],b[nn],c[nn];
int L[nn*4],R[nn*4],sum[nn*4];
void push_up(int id)
{
    sum[id]=sum[id<<1]+sum[id<<1|1];
}
void build(int id,int l,int r)
{
    L[id]=l,R[id]=r;
    if(l==r)
    {
        sum[id]=1;
        return ;
    }
    int m=(l+r)>>1;
    build(2*id,l,m);
    build(2*id+1,m+1,r);
    push_up(id);
}
void update(int  id,int x)
{
    if(L[id]==R[id])
    {
        sum[id]=0;
        return ;
    }
    int m=(L[id]+R[id])>>1;
    if(x<=m)
        update(2*id,x);
    else
        update(2*id+1,x);
    push_up(id);
}
int query(int id,int l,int r)
{
    if(L[id]>=l&&R[id]<=r)
    {
        return sum[id];
    }
    int m=(L[id]+R[id])>>1;
    int re=0;
    if(l<=m)
        re+=query(2*id,l,r);
    if(r>m)
        re+=query(2*id+1,l,r);
    return re;
}
int ask(int id,int x)
{
    if(L[id]==R[id])
    {
        sum[id]=0;
        return L[id];
    }
    int re;
    if(sum[2*id]>=x)
        re=ask(2*id,x);
    else
        re=ask(2*id+1,x-sum[2*id]);
    push_up(id);
    return re;
}
int main()
{
    int i,x;
    while(scanf("%d",&n)!=EOF)
    {
        build(1,0,n-1);
        for(i=1;i<=n;i++)
        {
            scanf("%d",&x);
            update(1,x);
            a[i]=query(1,0,x);
        }
        build(1,0,n-1);
        for(i=1;i<=n;i++)
        {
            scanf("%d",&x);
            update(1,x);
            b[i]=query(1,0,x);
        }
        int ix=0;
        build(1,0,n-1);
        for(i=n;i>=1;i--)
        {
            c[i]=(a[i]+b[i]+ix)%(n-i+1);
            ix=(a[i]+b[i]+ix)/(n-i+1);
        }
        for(i=1;i<=n;i++)
        {
            printf("%d%c",ask(1,c[i]+1),i==n?'\n':' ');
        }
    }
    return 0;
}



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值