康托展开

康托展开

标签(空格分隔): C++ 数论数学


提示

建议先明白排列:\(A_n^r\):计算方式: n!/(n-r)!
特殊地,我们将r=n的排列称为全排列

废话

假设学校进行期末考试,然而管理层的某人比较闲,为了不让同学们知道自己的实际排名,他给同学们的号码牌是由1n排列而成的,例如:每个人的号码为5位,数字为15。而我们的同学也很闲,他们很想知道自己到底多少名,于是......草稿纸和笔开始工作了,然而这时,试想你突然站出来,用10秒算出所有人的排名,那你是不是......嘿嘿嘿

言归正传:
处理这种排列序号的问题,数学上由康托展开来完美KO。

康托展开

假设n为5,你的序号为42135,此时我们如何计算名次?(序列号)

考虑:从最高位到最低位进行分析:

  1. 第一位为4,那么在1,2,3,5中,比4小的有3个,那以1,2,3开头的序列就有:3*\(A_4^4\)种,此时比42135小的就有了72种。
  2. 第二位为2,那么在余下的1,3,5中,比2小的有1,那么此时比42135小的又多了1*\(A_3^3\),有6种。
  3. 第三位为1,余下的3,5中没有比它小的,那么贡献为:0*\(A_2^2\)
  4. 第四位为3,余下的5中没有比它小的,贡献:0*\(A_1^1\)
  5. 第五位为5,固定下来了。贡献:0*\(A_0^0\)
  6. 结束,答案为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;
}
posted @ 2021-01-25 16:26  fallingdust  阅读(101)  评论(0编辑  收藏  举报