康托展开
康托展开是一个全排列到一个自然数的双射,常用于构建哈希表时的空间压缩。 康托展开的实质是计算当前排列在所有由小到大全排列中的顺序,因此是可逆的。 --百度百科
康托正展开
看定义很花哨,但实际上十分简单。举个例子:
原数字序列为: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

浙公网安备 33010602011771号