康托展开

康托展开是一个全排列一个自然数双射
即可以计算出 1 ~ n的全排列中字典序为k的排列,也可以计算出给定的排列在全排列中的排名


康托展开

康托展开计算一个给定的 1 ~ n 排列的排名的公式为
1 + ∑ i = 1 n A i ( n − i ) ! 1+\sum_{i=1}^n A_i(n-i)! 1+i=1nAi(ni)!
其中 A i A_i Ai表示该排列中第i位后比第i位数字小的数的个数

即假设该排列前 1 ~ i 位不变
如果把第i位后比第i位小的数与第i位交换
那么第 i+1 ~ n 位的数无论如何排列,最后得到的排列都会比原排列小
即总共有 A i ( n − i ) ! A_i(n-i)! Ai(ni)!种排法
每一位都如此计算后相加就得到排名在该排列之前的个数

洛谷P5367 【模板】康托展开

#include<iostream>
#include<cstdio>
#include<cmath>
#include<queue>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long lt;
#define lowbit(x) ((x)&(-x))

int read()
{
    int f=1,x=0;
    char ss=getchar();
    while(ss<'0'||ss>'9'){if(ss=='-')f=-1;ss=getchar();}
    while(ss>='0'&&ss<='9'){x=x*10+ss-'0';ss=getchar();}
    return f*x;
}

const int mod=998244353;
const int maxn=2000010;
int n;
int a[maxn],fac[maxn],sum[maxn];

void add(int x,int v){ for(int i=x;i<=n;i+=lowbit(i)) sum[i]+=v;}
int qsum(int x){ int res=0; for(int i=x;i>0;i-=lowbit(i))res+=sum[i]; return res;}

int main()
{
	n=read();
	for(int i=1;i<=n;++i)
	a[i]=read();
	
	fac[0]=1;
	for(int i=1;i<=n;++i)
	fac[i]=1ll*fac[i-1]*i%mod;
	
	int ans=0;
	for(int i=n;i>=1;--i)
	{
		ans=(ans+1ll*fac[n-i]*qsum(a[i])%mod)%mod;
		add(a[i],1);
	} 
	
	printf("%d\n",ans+1);
	return 0;
}

逆康托展开

给定排名求排列就是康托展开的逆向过程

对第i位的数,以排名除以 ( n − i ) ! (n-i)! (ni)!得到余数r和商q
那么第i位就是剩余数字中第q+1小的值
之后以余数r继续作为排名计算下一位

比如 1 ~ 4 的排列中排名为10的
即所求排列前面有9个字典序更小的排列
9/3! 得 q=1 ,r=3,说明所求排列第一位之后只有一个比第一位小的,所以第一位是2
3/2! 得 q=1 ,r=1,说明第二位之后只有一个更小的数,即剩余数(1,3,4)中第2小的,所以第二位是3
1/1! 得 q=1, r=0,说明第三位之后也只有一个更小的数,所以第三位是4
最后一位只剩下1
所以所求排列即 2 3 4 1

UVA - 11525 Permutation
这题给的排名计算公式恰好就是康托展开
所以直接用treap等维护找出剩余数字第s+1小即可

#include<iostream>
#include<cstdio>
#include<cmath>
#include<queue>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long lt;
#define lowbit(x) ((x)&(-x))

int read()
{
    int f=1,x=0;
    char ss=getchar();
    while(ss<'0'||ss>'9'){if(ss=='-')f=-1;ss=getchar();}
    while(ss>='0'&&ss<='9'){x=x*10+ss-'0';ss=getchar();}
    return f*x;
}

const int maxn=100010;
int T,n;
int val[maxn],rnd[maxn];//,fa[maxn];
int ch[maxn][2],size[maxn],rt,tot;

void update(int p){ size[p]=size[ch[p][0]]+size[ch[p][1]]+1;}

void rotate(int &p,int d)
{
	int k=ch[p][d^1];
	ch[p][d^1]=ch[k][d];
	ch[k][d]=p;
	update(p); update(k);
	p=k;
}

void ins(int &p,int x)
{
	if(!p){
		p=++tot; val[p]=x;
		rnd[p]=rand(); size[p]=1;
		return;
	}
	int d=x<val[p]?0:1;
	ins(ch[p][d],x);
	if(rnd[ch[p][d]]<rnd[p]) rotate(p,d^1);
	update(p);
}

void del(int &p,int x)
{
	if(x==val[p])
	{
		if(!ch[p][0]) p=ch[p][1];
		else if(!ch[p][1]) p=ch[p][0];
		else
		{
			int dd=rnd[ch[p][0]]<rnd[ch[p][1]]?1:0;
			rotate(p,dd); del(ch[p][dd],x);
		}
	}
	else if(x<val[p]) del(ch[p][0],x);
	else del(ch[p][1],x);
	if(p) update(p);
}

int kth(int p,int k)
{
	int ss=size[ch[p][0]];
	if(k==ss+1) return val[p];
	else if(k<=ss) return kth(ch[p][0],k);
	else return kth(ch[p][1],k-ss-1);
}

void init()
{
	rt=tot=0;
	memset(size,0,sizeof(size));
	memset(ch,0,sizeof(ch));
}

int main()
{
	T=read();
	while(T--)
	{
		init(); n=read();
		for(int i=1;i<=n;++i) ins(rt,i);
		for(int i=1;i<=n;++i)
		{
			int s=read();
			int x=kth(rt,s+1);
			del(rt,x);
			printf("%d",x);
			if(i!=n) printf(" ");
		}
		printf("\n");
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值