考虑到本文读者年龄原因,本文改为使用简体中文撰写。
题目描述
今有正整数
n
,
k
n,k
n,k,求
1
−
n
1-n
1−n 共
n
n
n 个数的全排列,按字典序的第
k
k
k 个。
数据满足
1
≤
n
≤
1
0
5
,
1
≤
k
≤
min
(
n
!
,
1
0
20
000
)
.
1\leq n\leq10^5,1\leq k\leq\min(n!,10^{20\ 000}).
1≤n≤105,1≤k≤min(n!,1020 000).
Solution \text{Solution} Solution
请注意 k k k 的上限! ls忘了打。这很重要!
对于 50 % 50\% 50% 及更低的数据,ls已经解释得很详尽,在此无需赘述。
尝试使用 DeCantor Expansion
(逆康托展开)算法求解。这篇文章有详细解释,看不懂找 ls 他英语一级棒。时间复杂度
O
(
n
2
)
O(n^2)
O(n2);
预处理
1
−
n
1-n
1−n 的阶乘,时间复杂度
O
(
n
2
)
O(n^2)
O(n2);
朴素的高精度除法,时间复杂度
O
(
n
2
)
O(n^2)
O(n2);
综上所述,总的时间复杂度为 O ( n 2 ) O(n^2) O(n2)。贴上代码。
#include<cstdio>
#include<cstdlib>
#include<cstring>
#define reg register
typedef long long ll;
const int MAXN=1010; //最长 1000位
const int BYTE=8; //压8位,也就是说一个int代表8位数
const int MB=MAXN/BYTE+1; //新的长度
int pw[BYTE+10]; //pw[i]表示10^i
char str[MAXN+10];
struct Bignum{ //高精度类
int a[MB+10];
Bignum(){
memset(a,0,sizeof(a));
}
void read(){ //读入、赋值、压位
scanf("%s",str+1);
int len=strlen(str+1);
for(reg int i=1;i<=len;++i)
a[i]=str[len-i+1]-'0';
for(reg int i=1;i<=len/BYTE+1;++i){
int cnt=0;
for(reg int j=i*BYTE;j>=(i-1)*BYTE+1;--j)
cnt=cnt*10+a[j];
a[i]=cnt;
}
//注意清零
for(reg int i=len/BYTE+2;i<=len;++i) a[i]=0;
}
void print(){ //打印一个数
bool tf=0;
for(reg int i=MB+1;i>=1;--i){
if(a[i]&&!tf){
tf=1;
printf("%d",a[i]);
continue;
}
if(tf) printf("%08d",a[i]);
}
if(!tf) putchar('0');
}
//重定义Bignum类的运算,高精+高精
friend Bignum operator+(const Bignum a,const Bignum b){
Bignum c;
for(reg int i=1;i<=MB+1;++i)
c.a[i]=a.a[i]+b.a[i];
for(reg int i=1;i<=MB+1;++i)
if(c.a[i]>=pw[BYTE]){
c.a[i+1]+=c.a[i]/pw[BYTE];
c.a[i]%=pw[BYTE];
}
return c;
}
//高精减
friend Bignum operator-(const Bignum a,const Bignum b){
Bignum c;
for(reg int i=1;i<=MB+1;++i)
c.a[i]=a.a[i]-b.a[i];
for(reg int i=1;i<=MB+1;++i)
if(c.a[i]<0){
c.a[i]+=10;
--c.a[i+1];
}
return c;
}
//高精乘低精
friend Bignum operator*(const Bignum a,const int b){
Bignum c;
ll d[MB+10];
for(reg int i=1;i<=MB+1;++i)
d[i]=(ll)a.a[i]*b;
for(reg int i=1;i<=MB+1;++i){
if(d[i]>=pw[BYTE]){
d[i+1]+=d[i]/pw[BYTE];
d[i]%=pw[BYTE];
}
c.a[i]=(int)d[i];
}
return c;
}
//注意此处的>等价于>=
friend bool operator>(const Bignum a,const Bignum b){
for(reg int i=MB+1;i>=1;--i)
if(a.a[i]!=b.a[i])
return a.a[i]>b.a[i];
return 1;
}
}ft[MB+10],k,one;
//ft[i]表示i的阶乘
struct Pair{ //两个大数
Bignum a,b;
};
int n;
bool tf[MAXN+10]; //tf[i]表示第i个数是否可以被取用
int ans[MAXN+10]; //ans是答案数组
//朴素高精除,返回的a是商,b是余数
Pair operator/(const Bignum a,const Bignum b){
Pair c;Bignum d;
for(reg int i=MB+1;i>=1;--i){
d=d*10;d.a[1]=a.a[i];
c.a=c.a*10;
while(d>b){
d=d-b;
++c.a.a[1];
}
}
c.b=d;
return c;
}
void reset(){ //初始化
pw[0]=1;
for(reg int i=1;i<=BYTE;++i)
pw[i]=pw[i-1]*10;
ft[1].a[1]=1;
for(reg int i=2;i<=n;++i)
ft[i]=ft[i-1]*i;
memset(tf,1,sizeof(tf));
one.a[1]=1;
k=k-one;
}
int cg(const Bignum a){ //将一个Bignum转换成int
int cnt=0;
for(reg int i=MB+1;i>=1;--i)
cnt=cnt*pw[BYTE]+a.a[i];
return cnt;
}
int find(int x){ //找到能使用的第x小的数
int cnt=0;
for(reg int i=1;i<=n;++i)
if(tf[i]){
++cnt;
if(cnt==x){
tf[i]=0; //取用它
return i;
}
}
return -1;
}
void work(){ //DeCantor Expansion
Pair p;
int cnt;
//此处参考算法解释的文章
for(reg int i=1;i<n;++i){
p=k/ft[n-i];k=p.b;
cnt=cg(p.a);
ans[i]=find(cnt+1);
}
ans[n]=find(1); //最后只剩一个数了,取用它
}
int main(){
scanf("%d",&n);k.read();
reset();
work();
for(reg int i=1;i<=n;++i)
printf("%d ",ans[i]);
}
最后一个点的
100
000
100\ 000
100 000,看上去很吓人,但是由于
k
≤
1
0
20
000
k\leq10^{20\ 000}
k≤1020 000,所以前
96
000
96\ 000
96 000 位左右都是原来的顺序(本质上还是
n
2
n^2
n2 的)。这个点的特判我不管了你自己写。
优化方法
- 使用树状数组维护一个数是否被取用。时间复杂度 O ( n log n ) O(n\ \log\ n) O(n log n);
- 快速多项式除法,或者二分答案。时间复杂度 O ( n log n ) O(n\ \log\ n) O(n log n);
- 使用压位存储。若压 B Y T E BYTE BYTE 位,则时间复杂度 O ( O ( 原 来 ) B Y T E 2 ) O(\frac{O(原来)}{BYTE^2}) O(BYTE2O(原来));
- 使用任意模数快速阶乘。时间复杂度 O ( n log n ) O(n\ \log\ n) O(n log n)(未实践)。
期望时间复杂度 O ( n log n ) O(n\ \log\ n) O(n log n)。若哪位巨佬实现了,请评论并收下我的膝盖orz。