康托展开
康托展开
标签(空格分隔): C++ 数论数学
提示
建议先明白排列:\(A_n^r\):计算方式: n!/(n-r)!
特殊地,我们将r=n的排列称为全排列
废话
假设学校进行期末考试,然而管理层的某人比较闲,为了不让同学们知道自己的实际排名,他给同学们的号码牌是由1n排列而成的,例如:每个人的号码为5位,数字为15。而我们的同学也很闲,他们很想知道自己到底多少名,于是......草稿纸和笔开始工作了,然而这时,试想你突然站出来,用10秒算出所有人的排名,那你是不是......嘿嘿嘿
言归正传:
处理这种排列序号的问题,数学上由康托展开来完美KO。
康托展开
假设n为5,你的序号为42135,此时我们如何计算名次?(序列号)
考虑:从最高位到最低位进行分析:
- 第一位为4,那么在1,2,3,5中,比4小的有3个,那以1,2,3开头的序列就有:3*\(A_4^4\)种,此时比42135小的就有了72种。
- 第二位为2,那么在余下的1,3,5中,比2小的有1,那么此时比42135小的又多了1*\(A_3^3\),有6种。
- 第三位为1,余下的3,5中没有比它小的,那么贡献为:0*\(A_2^2\)。
- 第四位为3,余下的5中没有比它小的,贡献:0*\(A_1^1\)。
- 第五位为5,固定下来了。贡献:0*\(A_0^0\)。
- 结束,答案为72+6+1。
注意,78只是比你小的,你的位置是78+1。
另外:12345的序号为0(每一位贡献均为0),但是它在实际中为1。
逆康托展开
对于一个由1~n构成的序列,给出它的序列号,求出它的序列。
这里直接开栗子:
如果初始序列是12345(第一个),让你求第79个序列是什么。(按字典序递增)
这样计算:
先把79减1,因为康托展开里的初始序列编号为0
然后计算下后缀积:
1 2 3 4 5
5! 4! 3! 2!1! 0!
120 24 6 2 1 1
78 / 4! = 3 ······ 6 有4个比它小的所以因该是4 从(1,2,3,4,5)里选
6 / 3! = 1 ······ 0 有1个比它小的所以因该是2 从(1, 2, 3, 5)里选
0 / 2! = 0 ······ 0 有2个比它小的所以因该是1 从(1, 3, 5)里选
0 / 1! = 0 ······ 0 有0个比它小的所以因该是3 从(3,5)里选
0 / 0! = 0 ······ 0 有0个比它小的所以因该是5 从(5)里选
所以编号79的是 42135
#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
int fac[25]={1};
int n,k,x;
int val[25];
bool vis[25];
void reverse_contor(int x){
memset(vis,0,sizeof vis);
x--;
int j;
for(int i=1;i<=n;i++){
int t=x/fac[n-i];
for(j=1;j<=n;j++){
if(!vis[j]){
if(!t) break;
t--;
}
}
printf("%d ",j);
vis[j]=1;
x%=fac[n-i];
}
puts("");
}
int contor(int x[]){
int p=0;
for(int i=1;i<=n;i++){
int t=0;
for(int j=i+1;j<=n;j++){
if(x[i]>x[j]) t++;
}
p+=t*fac[n-i];
}
return p+1;
}
int main(){
scanf("%lld%lld",&n,&k);
for(int i=1;i<=n;i++)
fac[i]=fac[i-1]*i;
while(k--){
char ch;
cin>>ch;
if(ch=='P') scanf("%lld",&x),reverse_contor(x);
else{
for(int i=1;i<=n;i++) scanf("%lld",&val[i]);
printf("%lld\n",contor(val));
}
}
return 0;
}