返回顶部

康托展开

康托展开

应用:用来求一个求 1∼N 的一个给定全排列在所有1∼N 全排列中的排名。可以在 \(O(n^2)\) 的复杂度内求出一个排列的排名,在用到树状数组优化时可以做到 \(O(nlog n)\)
举个例子:
当n=3时,一共有n!=6种组合。所有组合按照字典需排序如下:

123
132
213
231
312
321

那么

f("123") = 1
f("132") = 2
...
f("321")=6

下面,我将解释这个算法的原理,首先给出康托展开的公式:
给定全排列s,则\(f(s)=a_n(n-1)!+a_{n-1}(n-2)!+...+a_1*0!+1\)
其中!表示阶乘符号,\(n!=n*(n-1)*...*2*1\)
\(a_i\)表示前面i-1位未出现过的且比s[i]小的数的数量。
举个例子:令s="24153"
第一位是2,比它小的就只有一个1了,所以只有1放在这一位,才会比它小,那后面的呢?你爱怎么排怎么排!后面有4个数,所以一共有4!种排列方法,也就是说一共有1×4!种方法。
第二位是4,比它小的有1,2,3,但是2已经在前面出现过了,所以能放在这一位的就只有1,3这2个数,而后面呢,有3个数,就有3!种排列方法,所以一共有
2×3!种方法。
第三位的1没有更小的啦,所以一共是0×2!种方法。
第四位在5后面有比5小的有3,一共1个数,1×1!种方法。
第五位其实不用考虑了,都用过了。
那么f(s)=1×4!+2×3!+0×2!+1×1!+0×0!+1=37,所以该排列的排名位37名。
\(a_i\)和前i-1位数有关,类似动态前缀和,那么我们就可以用树状数组进行维护,每次更新只需要logn的时间,大大优化了时间复杂度。

P5367 【模板】康托展开

#include<iostream>
#include<cstdio>
using namespace std;
typedef long long ll;//虽然不知道这题不开long long会不会见祖宗,但保险起见还是开一下吧
ll tree[1000005];//树状数组
int n;
int lowbit(int x){
	return x&-x;
}
void update(int x,int y){
	while(x<=n){
		tree[x]+=y;
		x+=lowbit(x);	
	}
}
ll query(int x){
	ll sum=0;
	while(x){
		sum+=tree[x];
		x-=lowbit(x);	
	}
	return sum;
}
const ll mod=998244353;
ll jc[1000005]={1,1};//存阶乘的数组
int a[1000005];
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++){//预处理阶乘数组和树状数组
		jc[i]=(jc[i-1]*i)%mod;
		update(i,1);
	}
	ll ans=0;
	for(int i=1;i<=n;i++){
		scanf("%d",&a[i]);
		ans=(ans+((query(a[i])-1)*jc[n-i])%mod)%mod;
		update(a[i],-1);
	}
	printf("%lld",ans+1);//别忘了+1
   return 0;
}

逆康托展开

如果N=5,让你求第111个序列是什么

首先将111减一

然后计算1~N的所有阶乘为多少

1 2 3 4 5
1! 2! 3! 4! 5!
1 2 6 24 120

110 / 4! = 4 ······ 14 从(1,2,3,4,5)里选 有4个比它小的所以因该是5
14 / 3! = 2 ······ 2 从(1, 2, 3, 4)里选 有2个比它小的所以因该是3
2 / 2! = 1 ······ 0 从(1, 2, 4)里选 有1个比它小的所以因该是2
0 / 1! = 2 ······ 0 从(1,4)里选 有0个比它小的所以因该是1
0 / 0! = 0 ······ 0 从(4)里选 有0个比它小的所以因该是4

所以编号111的应该是53214。

代码为:

#include<iostream>
#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
long long jc[25];
int x,n;
void init(int n){
	jc[0] = jc[1] = 1;
    for(int i=2;i<=n;i++)jc[i] = i*jc[i-1];
}

vector<int> vec; //存需要排列的字符
void rev_KT(long long k){
    k--;
    if(k==0){
    	for(int i=1;i<=n;i++)
    	cout<<i<<' ';
    	return;
	}
    int len=vec.size();
    
    for(int i=1;i<=len;i++){
        int t=k/jc[n-i];
        k%=jc[n-i];
        cout<<vec[t]<<' ';
        vec.erase(vec.begin()+t);
    }
}
int main(){
    
    
    cin>>n;
    init(n);
    long long x;
    cin>>x;

    for(int i=1;i<=n;i++)
        vec.push_back(i);
    rev_KT(x);
    cout<<endl;
    vec.clear();
}

P3014 [USACO11FEB] Cow Line S

#include<bits/stdc++.h>
using namespace std;
long long jc[25];
int a[25];
int n,m;
void init(){
	jc[0] = jc[1] = 1;
	for(int i=2;i<=n;i++)
	jc[i] = jc[i-1]*i;
}
vector<int> vec; //存需要排列的字符
void rev_KT(long long k){
    k--;
    if(k==0){
    	for(int i=1;i<=n;i++)
    	cout<<i<<' ';
    	return;
	}
    int len=vec.size();
    vector<int>q;
    for(int i=1;i<=len;i++){
        int t=k/jc[n-i];
        k%=jc[n-i];
        cout<<vec[t]<<' ';
        vec.erase(vec.begin()+t);
    }
}
long long tree[1000005];//树状数组
int lowbit(int x){
	return x&-x;
}
void update(int x,int y){
	while(x<=n){
		tree[x]+=y;
		x+=lowbit(x);	
	}
}
long long  query(int x){
	long long sum=0;
	while(x){
		sum+=tree[x];
		x-=lowbit(x);	
	}
	return sum;
}
int main(){
	ios::sync_with_stdio(0);
    cin.tie(0);
	
	cin>>n>>m;
	init();
	char s;
	while(m--){
		cin>>s;
		if(s=='P'){
			long long x;
			cin>>x;
			
			for(int i=1;i<=n;i++)
				vec.push_back(i);
			rev_KT(x);
			cout<<endl;
			vec.clear();	
		}
		else{
			long long ans = 0;
			for(int i=1;i<=n;i++)
				update(i,1);
			for(int i=1;i<=n;i++){
				cin>>a[i];
				ans=(ans+((query(a[i])-1)*jc[n-i]));
				update(a[i],-1);
			} 
			cout<<ans+1<<endl;
		}
	}
}
posted @ 2023-10-28 00:03  supperwriter  阅读(75)  评论(0)    收藏  举报