前置知识:康拓展开
逆康拓展开可以求解
1
1
1 ~
n
n
n的全排列中,字典序第
x
x
x个的排列。
逆康拓展开
比如,在
1
1
1 ~
5
5
5的全排列当中,要求按字典序第
107
107
107的排列。
因为在康拓展开时,单调递增的序列算出的结果是
0
0
0,所以要将原数减一。
首先先让
x
/
x/
x/
(
n
−
1
)
!
(n-1)!
(n−1)!,也就是
106
/
(
5
−
1
)
!
=
106
/
4
!
=
106
/
24
=
4
…
…
10
106/(5-1)!=106/4!=106/24=4……10
106/(5−1)!=106/4!=106/24=4……10,那么就算出有
4
4
4个值比第一个值小,所以第一个值就是
5
5
5。
接着把上次剩下的余数拿来继续除
10
/
(
4
−
1
)
!
=
10
/
3
!
=
10
/
6
=
1
…
…
4
10/(4-1)!=10/3!=10/6=1……4
10/(4−1)!=10/3!=10/6=1……4,只有一个数比第二个值小,那么第二个值就是
2
2
2。
下一次,
4
/
(
3
−
1
)
!
=
4
/
2
!
=
4
/
2
=
2
4/(3-1)!=4/2!=4/2=2
4/(3−1)!=4/2!=4/2=2,有两个数比第三个值小,那么第三个值就是
4
4
4,这时候
2
2
2已经被排过了,现在还没有排的元素是
1
1
1,
3
3
3,
4
4
4,故第三大的是
4
4
4。
接着,
0
/
(
2
−
1
)
!
=
0
/
1
=
0
0/(2-1)!=0/1=0
0/(2−1)!=0/1=0,那就是当前没拍过的元素中第
1
1
1大,所以是
1
1
1。
最后就只剩一个
3
3
3,那么最后排完就是
52413
52413
52413
思想就是康拓展开公式的逆用。
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5;
int read(){
int x=0,f=1;
char c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+c-'0',c=getchar();
return x*f;
}
void print(int x){
if(x<0)putchar('-'),x=-x;
if(x<10){putchar(x+'0');return;}
print(x/10);
putchar(x%10+'0');
}
int n,k;
int a[N];
int f[N]={1};
signed main(){
n=read(),k=read()-1;
for(int i=1;i<=n;i++)a[i]=i,f[i]=f[i-1]*i;
string s="";
for(int i=1;i<=n;i++){
int t=k/f[n-i];
print(a[t+1]),putchar(' ');
for(int i=t+1;i<=n;i++)a[i]=a[i+1];
k%=f[n-i];
}
}
或者也可以用动态数组。
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5;
int read(){
int x=0,f=1;
char c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+c-'0',c=getchar();
return x*f;
}
void print(int x){
if(x<0)putchar('-'),x=-x;
if(x<10){putchar(x+'0');return;}
print(x/10);
putchar(x%10+'0');
}
int n,k;
vector<int>a;
int f[N]={1};
signed main(){
n=read(),k=read()-1;
for(int i=1;i<=n;i++)a.push_back(i),f[i]=f[i-1]*i;
string s="";
for(int i=1;i<=n;i++){
int t=k/f[n-i];
print(a[t]),putchar(' ');
a.erase(a.begin()+t);
k%=f[n-i];
}
}
例题
Cow Line S
这是一道康拓展开加逆康拓展开的板子题。
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=2e6+5;
int n,m,k;
vector<int>a;
int g[N];
int f[N]={1};
int bit[N];
void update(int x,int p){
while(x<=n){
bit[x]+=p;
x+=x&-x;
}
}
int query(int x){
int res=0;
while(x){
res+=bit[x];
x-=x&-x;
}
return res;
}
signed main(){
ios::sync_with_stdio(0);
cin>>n>>m;
for(int i=1;i<=n;i++)f[i]=f[i-1]*i;
while(m--){
char op;
cin>>op;
if(op=='P'){
cin>>k;
k--;
string s="";
for(int i=1;i<=n;i++)a.push_back(i);
for(int i=1;i<=n;i++){
int t=k/f[n-i];
cout<<a[t]<<' ';
a.erase(a.begin()+t);
k%=f[n-i];
}
cout<<'\n';
}
else{
for(int i=1;i<=n;i++)cin>>g[n-i+1];
int ans=0;
for(int i=1;i<=n;i++){
int res=query(g[i]-1);
ans+=f[i-1]*res;
update(g[i],1);
}
cout<<ans+1<<'\n';
for(int i=1;i<=n;i++)update(g[i],-1);//记得清空树状数组
}
}
}