康托展开 and 逆康托展开

文章讲述了康托展开在计算排列排名中的应用,包括其公式解释、举例说明以及如何用树状数组优化。同时介绍了逆康托展开的过程,并给出了相关的AC代码示例。
摘要由CSDN通过智能技术生成

前记:很早之前学过,一直没用上,就懒得写了,今天比赛遇见了,现场推了半个多小时推出来了,但是可能因为取模问题(可能是数据问题,体验感极差)wa了。晚上复习一下,以免忘记。

康托展开:用于求某个排列在全排列中的排名,也就是第几大。

例如:

排列个数为3时
1 2 3    //第1个
1 3 2    //第2
2 1 3    //第3
2 3 1    //第4
3 1 2    //第5
3 2 1    //第6

先给出康托展开的公式:ans = 1 + sum( P(a[i]) * (n-i)! ); (1<= i <= n)

其中P(a[i])表示a[i]后面比a[i]大的数的个数,(n-i)!表示n-i的阶乘;有点抽象;

如何理解呢,举个例子:3 1 2

对于3:它后面比它小的有1,2,两个数,也就是2!* 2 = 4;

对于1:它后面比它小的没有,也就是零个;

对于2:和1一样;

所以答案等于4+1 = 5;可以试着推一下;

其实也就是,对于第i个位置,如果x < a[i],那么不管它后面的数如何排列,这个排列一点会比该排列小,也就是后面比它小的数 P(a[i]) * (n-i)!,找a[i]后面比它小的数的个数这一过程可以用树状数组优化,下面是洛谷模板以及ac代码;

康托展开模板

AC code:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
//#define int ll
#define PII pair<int,int>
#define endl "\n" 

const int N = 1e6+100;
const int inf = 0x3f3f3f3f; 
const ll INF = 2e18;
const int mod = 998244353; 
const int MOD = 998244353;

/*
预处理阶乘
*/ 

struct bit{
	int n;
	vector<int>num;
	bit(int n):n(n),num(n+1,0){}//初始化 
	int lowbit(int x){
    	return x&(-x);
	}
	void add(int i,int v){    //在i位置加上k
    	while(i <= n){
        	num[i] += v;
        	i += lowbit(i);
    	}
	}
	int get(int i){//求A[1 - i]的和
    	int res = 0;
    	while(i > 0){
        	res += num[i];
        	i -= lowbit(i);
    	}
    	return res;
	}
};

ll fac[N],a[N];

void solve(){
	int n;cin>>n;
	bit t(n+1);
	fac[0] = 1;
	for(int i=1;i<=n;i++){
		fac[i] = i * fac[i-1] % mod;
		cin>>a[i];
		t.add(a[i], 1);
	}
	
	ll res = 0,r = n-1;
	for(int i=1;i<=n;i++){
		t.add(a[i],-1);
		res = (res + t.get(a[i]) * fac[r--] % mod) % mod;
	}
	cout<<res + 1<<endl;
}

signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);cout.tie(nullptr);
    int t=1;
    //init(); 
    
    //cin >> t; 
    while(t--)solve();
    return 0;
}

由于每个排列和该排列的排名是一一对应,所以这个过程是可逆的,给你一个排列,一定能得到这个排列的排名,也就是逆康托展开;

具体操作:假设要找第x个排列,

(1)首先将x - 1;

(2)然后令 t = x / fac[n-1],其中fac[n-1]就是n-1的阶乘,得到的t就是康托展开中的在i后面比a[i]小的数的个数,由此就能推出这个数是几;

(3)再对x %= fac[n-1];

(4)对fac[n-2]继续(2)(3)操作即可;

理解了康托展开,逆康托展开就会很容易,下面是例题和ac代码:

逆康托展开

AC code:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define int ll
#define PII pair<int,int>
#define endl "\n" 

const int N = 1e6+100;
const int inf = 0x3f3f3f3f; 
const ll INF = 2e18;
const int mod = 998244353; 
const int MOD = 998244353;

/*
由于n很小,可以直接暴力找的,不需要树状数组优化
*/ 

struct bit{
	int n;
	vector<int>num;
	bit(int n):n(n),num(n+1,0){} 
	int lowbit(int x){
    	return x&(-x);
	}
	void add(int i,int v){    
    	while(i <= n){
        	num[i] += v;
        	i += lowbit(i);
    	}
	}
	int get(int i){
    	int res = 0;
    	while(i > 0){
        	res += num[i];
        	i -= lowbit(i);
    	}
    	return res;
	}
};

ll fac[N],a[N];
int vis[25];
int n,q;

void solve(){
	cin>>n>>q;
	bit t(n+1);
	fac[0] = 1;
	for(int i=1;i<=n;i++)fac[i] = fac[i-1] * i;
	
	auto find = [&] (int k){
		int c = 0,res;
		for(int i=1;i<=n;i++){
			if(vis[i])continue;
			c += 1;
			if(c == k){
				res = i;
				break;
			}
		}	
		vis[res] = 1;
		return res;
	};
	 
	while(q--){
		char op;cin>>op;
		if(op == 'P'){
			memset(vis,0,sizeof vis);
			int k;cin>>k;
			k -= 1;
			for(int i=n-1;i>=0;i--){
				cout<<find(k/fac[i] + 1)<<" ";
				k %= fac[i];
			}
			cout<<endl;
		}
		else {
			for(int i=1;i<=n;i++){
				cin>>a[i];
				t.add(a[i], 1);
			}
			int res = 1;
			for(int i=1;i<=n;i++){
				t.add(a[i], -1);
				res += t.get(a[i]) * fac[n-i];
			}
			cout<<res<<endl;
		}
	}
}

signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);cout.tie(nullptr);
    int t=1;
    //init(); 
    
    //cin >> t; 
    while(t--)solve();
    return 0;
}

  • 6
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值