NOIP 模拟题 排列

题目描述
小 G 喜欢玩排列。现在他手头有两个 n 的排列。n 的排列是由 0,1,2,…,n − 1
这 n 的数字组成的。对于一个排列 p,Order(p) 表示 p 是字典序第 Order(p) 小的
排列(从 0 开始计数) 。对于小于 n! 的非负数 x,Perm(x) 表示字典序第 x 小的排
列。
现在,小 G 想求一下他手头两个排列的和。两个排列 p 和 q 的和为 sum =
Perm((Order(p) + Order(q))%n!)。
输入格式
输入文件第一行一个数字 n,含义如题。
接下来两行,每行 n 个用空格隔开的数字,表示小 G 手头的两个排列。
输出格式
输出一行 n 个数字,用空格隔开,表示两个排列的和。

样例输入 1
2
0 1
1 0
样例输出 1
1 0

样例输入 2
3
1 2 0
2 1 0
3
样例输出 2
1 0 2

数据范围
1、2、3、4 测试点,1 ≤ n ≤ 10。
5、6、7 测试点,1 ≤ n ≤ 5000,保证第二个排列的 Order ≤ 10^5 。
8、9、10 测试点,1 ≤ n ≤ 5000。

40分,全排列暴力。
40+30分:对于中间的三个点,我们可以先求出第二个排列的Order,然后再用next_permutation()在第一个排列的基础上搞Order次就可以了。
100分:
学过排列公式的很容易理解。
我们用k[]数组,k[i]表示第i个数在ai~a[n]中排名第几(从0开始);
4 1 2 3 这个排列的k数组为 3 0 0 0 那么,这个排列的Order值就是3*A33+0*A22+0*A11;

我们把排列的k数组求出来,然后用类似高精度的方法相加即可求出需要求的排列的ck数组,然后就可以根据ck数组求出c排列了。

Order=k[1]* (n-1)!+k[2]* (n-2)!+k[3]*(n-3)!……
进位的时候,从第i位向第i-1位进位,需要取模和除以的进位制就是(n-i+1);(观察这个式子就可以得出,是提公因式吧)

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define LL long long
using namespace std;
const int N=5009;
int n,p1,p2,p3;
int a[N],b[N],c[N];
int ak[N],bk[N],ck[N];
bool used[N];
void pre()
{
    for(int i=1;i<=n;i++)
     for(int j=n;j>=i;j--)
      if(a[j]<a[i]) ak[i]++;
    for(int i=1;i<=n;i++)
     for(int j=n;j>=i;j--)
      if(b[j]<b[i]) bk[i]++;
}
void Order()
{
    for(int i=n-1;i>=1;i--)
    {
        ck[i]+=ak[i]+bk[i];
        ck[i-1]+=ck[i]/(n-i+1);
        ck[i]%=(n-i+1);
    }
}
void perm()
{
    for(int i=1;i<=n;i++) ck[i]++;
    for(int i=1;i<=n;i++)
    {
        int p=0;
        for(int j=0;j<n;j++)
        {
            if(!used[j])
            {
                p++;
                if(p==ck[i])
                {
                    c[i]=j,used[j]=1;
                    break;
                }

            }   
        }
    }
    for(int i=1;i<=n;i++) printf("%d ",c[i]);
}
int main()
{
    freopen("perm.in","r",stdin);
    freopen("perm.out","w",stdout);
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    for(int i=1;i<=n;i++) scanf("%d",&b[i]);
    pre();
    Order();
    perm();
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值