题目描述
有N头牛,分别用1……N表示,排成一行。
将N头牛,所有可能的排列方式,按字典顺序从小到大排列起来。
例如:有5头牛 1st: 1 2 3 4 5
2nd: 1 2 3 5 4
3rd: 1 2 4 3 5
4th : 1 2 4 5 3
5th : 1 2 5 3 4
……
现在,已知N头牛的排列方式,求这种排列方式的行号。
或者已知行号,求牛的排列方式。
所谓行号,是指在N头牛所有可能排列方式,按字典顺序从大到小排列后,某一特定排列方式所在行的编号。
如果,行号是3,则排列方式为1 2 4 3 5
如果,排列方式是 1 2 5 3 4 则行号为5
有K次问答,第i次问答的类型,由\(C_i\)来指明,\(C_i\)要么是‘P’要么是‘Q’。
当\(C_i\)为P时,将提供行号,让你答牛的排列方式。当\(C_i\)为Q时,将告诉你牛的排列方式,让你答行号。
解题思路
康托展开模板题
康托展开
我们假设数列是1 2 5 3 4
那我们模拟一下计算的方法
\(i=1\)时:\(ans+=0*4!\)
\(i=2\)时:\(ans+=0*3!\)
\(i=3\)时:\(ans+=2*2!\)
\(i=4\)时:\(ans+=0*1!\)
\(i=5\)时:\(ans+=0*0!\)
\(ans=4\)说明前面有4个数列,所以它就是第5个数列。
就是\(a[i]*frac(到i时还有几个比a[i]小的数没有出现)\),最终答案\(+1\)就好。
注意\(0!=1\)
康托逆展开
根据上面的方法,我们倒着做就好。
假设是第5个,那么前面就有4个数列。
\(i=1\)时:\(ans=\frac{4}{frac(4!)}=0\)
\(i=2\)时:\(ans=\frac{4}{frac(3!)}=0\)
\(i=3\)时:\(ans=\frac{4}{frac(2!)}=1\)
\(i=4\)时:\(ans=\frac{0}{frac(1!)}=0\)
\(i=5\)时:\(ans=\frac{0}{frac(0!)}=0\)
所以最终数列就是1 2 4 3 5
每次的被除数是上一次计算的余数,答案是结果向下取整,代表前面有几个数比它小还没有出现过。
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cstdio>
#define ll long long
using namespace std;
int n,k;
ll frac[]={1,1,2,6,24,120,720,5040,40320,362880,3628800,39916800,479001600,6227020800,87178291200,1307674368000,20922789888000,355687428096000,6402373705728000,121645100408832000,2432902008176640000};
int a[25];
bool vis[25];
int main(){
scanf("%d%d",&n,&k);
for(register int i=1;i<=k;i++){
char s[10];
scanf("%s",s);
if(s[0]=='Q'){
ll ans=0;
memset(vis,0,sizeof(vis));
for(register int j=1;j<=n;j++)scanf("%d",&a[j]);
for(register int j=1;j<=n;j++){
vis[a[j]]=1;
ll cnt=0;
for(register int k=1;k<a[j];k++){
if(!vis[k])cnt++;
}
ans+=cnt*frac[n-j];
}
printf("%lld\n",ans+1);
}
else {
memset(vis,0,sizeof(vis));
ll x;
scanf("%lld",&x);
x--;
for(register int j=1;j<=n;j++){
ll chu=x/frac[n-j],yu=x-chu*frac[n-j];
int cnt=0;
for(register int k=1;k<=n;k++){
if(!vis[k]){
cnt++;
}
if(cnt==chu+1&&!vis[k]){
printf("%d ",k);
vis[k]=1;
break;
}
}
x=yu;
}
putchar('\n');
}
}
}