1025 -康托展开&逆康托展开 - Cow Line[USACO11FEB]

传送门


分析

本来以为好难好难
结果除了不知道为什么,还是很简单的嘛[笑哭]
我们就记结论,并理解代码就好了


康托展开

先上定义:

康托展开是一个全排列到一个自然数的双射,常用于构建hash表时的空间压缩。设有n个数(1,2,3,4,…,n),可以有组成不同(n!种)的排列组合,康托展开表示的就是 当前排列组合在n个不同元素的全排列中的名次。

再讲作用:
对于一个具体的1~n的排列P,康托展开可以求出这个排列在 n!种有序排列中位于第几个,怎么求呢??机智的Contor告诉了我们答案:
X = A n ∗ ( n − 1 ) ! + A n − 1 ∗ ( n − 2 ) ! + ⋯ + A i ∗ ( i − 1 ) ! + ⋯ + A 2 ∗ 1 ! + A 1 ∗ 0 ! X=An∗(n−1)!+An−1∗(n−2)!+⋯+Ai∗(i−1)!+⋯+A2∗1!+A1∗0! X=An(n1)!+An1(n2)!++Ai(i1)!++A21!+A10!
(Ai表示排列 P 中第 i 个数的后面有多少个数小于当前的值)
这个算出来的就是在排列 P 前有X个排列小于它,那它的排名就是 X + 1 X+1 X+1

举个例子:
1~5的排列,1 3 2 5 4 排在第几个呢?
首先看看:第一个数字1之后没有比1小的数字,所以 An=0;在3之后有一个比它小的2,所以 A n − 1 = 1 A_{n−1}=1 An1=1。依此类推:
0∗(5−1)!+1∗(4−1)!+0∗(3−1)!+1∗(2−1)!+0∗(1−1)!=7
比这个排列还要小的排列有7个,所以它排在第8个。
下面我们枚举验证:

1 2 3 4 5
1 2 3 5 4
1 2 4 3 5
1 2 4 5 3
1 2 5 3 4
1 2 5 4 3
1 3 2 4 5
1 3 2 5 4


代码实现
ll contor(){
	ll res=0;
	for(int i=1;i<=n;++i){
		int t=0;
		for(int j=i+1;j<=n;++j)	if(a[j]<a[i]) t++;
		res+=t*1ll*fac[n-i];
	}
	return res+1;
}

很简单吧~~


逆康托展开

所谓逆康托展开,就是给出排列的名次X,求这个具体的排列P
再来公式:
先将X - 1,这能明白
然后 X / ( n − i ) ! X/(n-i)! X/(ni)!就得出了有多少个数小于排列P第 i 个位置上的数


举个例子
在(1,2,3,4,5)给出61可以算出排列组合为 34152。由上述的计算过程可以容易的逆推回来,具体过程如下:
用 61 / 4! = 2余13,说明a[5]=2,说明比首位小的数有2个,所以首位为3。
用 13 / 3! = 2余1,说明a[4]=2,说明在第二位之后小于第二位的数有2个,所以第二位为4。
用 1 / 2! = 0余1,说明a[3]=0,说明在第三位之后没有小于第三位的数,所以第三位为1。
用 1 / 1! = 1余0,说明a[2]=1,说明在第二位之后小于第四位的数有1个,所以第四位为5。
最后一位自然就是剩下的数2啦。
通过以上分析,所求排列组合为 34152。


代码
void reverse_contor(ll x){
	x--;int i,j;
	memset(vis,0,sizeof(vis));
	for(i=1;i<=n;++i){
		ll t=x/fac[n-i];
		for(j=1;j<=n;++j)
			if(!vis[j]){
				if(!t) break;
				t--;
			}
		b[i]=j;
		vis[j]=1;
		x%=fac[n-i];
	}
}

那么知识铺垫完啦,这道题就是板子题咯~~~
完整代码献上

#include<bits/stdc++.h>
#define ll long long
using namespace std;
int n,k,a[30],b[30];
ll fac[30];
bool vis[30]; 
void reverse_contor(ll x){
	x--;int i,j;
	memset(vis,0,sizeof(vis));
	for(i=1;i<=n;++i){
		ll t=x/fac[n-i];
		for(j=1;j<=n;++j)
			if(!vis[j]){
				if(!t) break;
				t--;
			}
		b[i]=j;
		vis[j]=1;
		x%=fac[n-i];
	}
}
ll contor(){
	ll res=0;
	for(int i=1;i<=n;++i){
		int t=0;
		for(int j=i+1;j<=n;++j)	if(a[j]<a[i]) t++;
		res+=t*1ll*fac[n-i];
	}
	return res;
}
int main(){
	scanf("%d%d",&n,&k);
	int i,j;
	fac[0]=1;
	for(i=1;i<=20;++i) fac[i]=fac[i-1]*1ll*i;
	ll q;char ch;
	for(i=1;i<=k;++i){
		cin>>ch;
		if(ch=='P'){
			scanf("%lld",&q);//查询1~n的排列中第 q 大的是多少
			reverse_contor(q); 
			for(j=1;j<=n;++j) printf("%d ",b[j]);
			printf("\n");
		}
		else{
			for(j=1;j<=n;++j)
				scanf("%d",&a[j]);
			printf("%lld\n",contor()+1);
		}
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值