题目描述
小 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;
}