康托展开

康托展开是一个全排列到一个自然数的双射,常用于构建哈希表时的空间压缩。 康托展开的实质是计算当前排列在所有由小到大全排列中的顺序,因此是可逆的。 --百度百科

康托正展开

看定义很花哨,但实际上十分简单。举个例子:
原数字序列为:1,2,3,4,求2143的排名
从前往后看:2的排名为\(2\),则在3排到第一位之前已经有\((4-1)*(4-2)*(4-3)\)中可能了(即1在第一位),之后以此类推,得出的就是答案。

为了方便使用,我们一般初始化fac(阶乘)数组

int fac[N];
inline void init(){
	fac[0]=1;
	for(int i=1;i<=n;i++)
		fac[i]=fac[i-1]*i;
}

之后再根据以上的口胡过程,写出代码

inline int contor(){
	long long ans=0;
	for(int i=1;i<=n;i++){
		int sum=0;
		for(int j=i+1;j<=n;j++)//找排名
			if(a[i]>a[j])sum++;
		ans=(ans+sum*(fac[n-i]))%mod;
	}
}
return ans+1;

我们就可以去写洛谷上的【模板】康托展开了!
然后你就会发现它的数据范围是\(1≤N≤1000000\),看了看自己O(n^2)陷入了沉思……

仔细观察可以发现:我们只能优化查询kth的过程,于是我们使用平衡树优化,看到有的神仙用vector优化但是我打了好几遍没过……看到有的神仙使用树状数组维护前缀积然后一波搞定,代码如下:

inline void sol(){
	for(int i=1;i<=n;i++)add(i,1);
	for(int i=1,x;i<=n;i++){
		ans=(fac[n-i]*(query(x)-1)+ans)%mod;/*至于query(x)-1是因为包含了自己*/
		add(x,-1);//删除
	}
}

于是你使用了比O(n)略大的复杂度切了这道题。

康托逆展开

原数字序列为:1,2,3,4,求第8大的数
首先\(8-1=7\)(因为计数是从0个开始的)
\(7/f[3]=1……1\)则比首位数小的有一位,所以首位数为2
\(1/f[2]=0……1\)则比第二位小的有零位,所以第二位为1
\(1/f[1]=1……0\)则比第三位小的有一位,所以第三位位4
然后只剩下了3
结果:2143

我们根据口胡过程写出代码

//这个代码没运行过,出锅了请告诉我
inline void sol(int x){
	x--;
	bool vis[N];//标记哪个数被取出来了.
	int cnt;
	long long ans=0;
	for(int i=0;i<n;i++){
		cnt=x/fac[n-i];
		x%=fac[n-i];
		for(int j=1;j<=n;j++){
			if(vis[j])continue;
			if(!cnt){
				vis[j]=1;
				printf("%d",ans);
				break;
			}
			cnt--;
		}
	}
}

然后我们就可以溜到洛谷里做板子题了:UVA11525 Permutation
很难看出:我们只能优化查询vis的过程。所以我们来进行树状数组倍增算法。(来源:洛谷光明正大)

首先初始化倍增数组

int lg[23];
inline void init(){
	lg[0]=1;
	for(int i=1;i<=19;i++){
		lg[i]=lg[i-1]<<1;
	}
}

然后是树状数组的板子啦(懒),再加一个查询,然后我直接复制粘贴我的码了。

int T,n,t[N],lg[25],ans,mx;
inline int lowbit(int x){
	return x&(-x);
}
inline void add(int x,int y){
	while(x<=n){
		t[x]+=y;
		x+=lowbit(x);
	}
}
inline int query(int x){
	int res=0;
	while(x){
		res+=t[x];
		x-=lowbit(x);
	}
	return res;
}
inline int ask(int x){
	int res=0;
	for(int i=mx;i>=0;i--){
		//qeury(res+lg[i])为填过的比当前将要填的数小的数的个数
		//res+lg[i]-query(res+lg[i])-2即为未填的比当前数小的数的个数
		if(res+lg[i]-query(res+lg[i])<=x)res+=lg[i];
	}
	return res+1;
}
int main(){
	T=read();
	init();//别忘记初始化,写的时候查了半天是挂在这了
	while(T--){
		n=read();mx=log2(n);
		memset(t,0,sizeof t);
		for(int i=1,x;i<=n;i++){
			x=read();
			ans=ask(x);
			add(ans,1);
			printf("%d",ans);
			if(i!=n)printf(" ");
		}
		puts("");
	}
	return 0;
}

【例题1】火星人
我直接STL
【例题2】PER

  • 题面:给定一个多重集(会有二位以上的数)的某次全排列,求是哪一次,由于可能会很大,所以%mod(mod 不一定是质数)
  • 于是我们去康CF1109E Sasha and a Very Easy Test
    • 【引题1】基本操作是一个普普通通的线段树,高级操作时对不一定为质数的mod的一波操作
    • 使用欧拉定理(扩展费马小定理)\(a^{ \varphi (p^2) }=1 (\ mod p)\)
    • 套入欧拉函数\(\varphi(n)=n* \pi _{i=1}^{n} p_i/(p_i-1)\)
    • 一个大于1的正整数一定可以拆分为\(p_1^{k1}+p_2^{k2}+p_3^{k3}+……+p_n^{kn}\),并且其中最多有一个\(p_x>sqrt(n)\),所以可以写出以下程序:
    inline int get_phi(int n){
    int ans=n;
    for(int i=2;i<=n*n;i++){
    	if(!(n%i)){
    		ans=1ll*ans*(i-1)*i;
    		while(!(n%i))n/=i;
    	}
    }
    if(n>1)ans=1ll*ans*(n-1)/n;
    return ans;
    }
    

先咕了,真的写不出来www

posted @ 2021-07-08 10:34  无琛  阅读(119)  评论(0)    收藏  举报