知识点 - 康托展开
解决问题类型:
问某个排列之后的第m个排列是什么?
讲义
康托展开
https://www.luogu.org/blog/bdcdcdc666/solution-p1088
1.康托展开
给出一个全排列,求它是第几个全排列(排列位次),叫做康托展开。
给出全排列长度和它是第几个全排列,求这个全排列,叫做逆康托展开。
2.康托展开解决问题
问某个排列之后的第m个排列是什么?
三步:
- 将排列变成排列位次(变进制数)
- 将排列位次(变进制数)加上m
- 将排列位次(变进制数)变成排列
3.利用变进制数实现康托展开
排列位次的公式为:
a i a_i ai表示原数的第i位在当前未出现的元素中是排在第几个.
将 ( a n a n − 1 . . . a 1 ) u n k n o w n (a_na_{n-1}...a_1)_{unknown} (anan−1...a1)unknown称为n进制数。
a数组的实现如下:
我们将初始排列 p n p n − 1 . . . p 1 p_np_{n-1}...p_1 pnpn−1...p1存在 a [ ] a[] a[]数组中。
$a_i 的 值 更 新 为 的值更新为 的值更新为 a_i$减去它左边比它小的数的数量-1
for(int i=1;i<=n;i++)
{
cin>>a[i];
int x=a[i];
for(int j=1;j<=a[i];j++)
x-=used[j];
//used[j]表示j是否用过(1用过,0没用)
used[a[i]]=1;
a[i]=x-1;
}
如果要转化成十进制:(其实没必要)
long long res=0;
for(int i=1;i<n;i++)
res=(res+a[i])*(n-i);
4.逆康托展开
按照进制转换的方法,将变进制数变回排列。
for(int i=n;i>0;i--)
{
a[i-1]+=a[i]/(n-i+1);
a[i]%=n-i+1;
}
5.树状数组/线段树优化
注意到
$a_i 的 值 更 新 为 的值更新为 的值更新为 a_i$减去它左边比它小的数的数量-1
可以用树状数组优化,对应的逆康托也可以用线段树搜索优化。
//从这儿开始,tt是长度,n是线段树大小
for(int i=1;i<=tt;i++)
{
scanf("%d",&a[i]);
upd(a[i]+n);
//upd更新一个节点
a[i]-=sum(1,a[i],1,n,1)+1;
//sum(x,y,l,r,root)=x到y的区间和,在l到r区间找,根节点在root
}
ni-----------
int fx(int l,int r,int x,int root)
//在l到r范围找出有x个0的位置
{
if(l==r)
return l;
int mid=(l+r)>>1,sss=sum(l,mid,l,r,root);
if(mid-l+1-sss>x)
return fx(l,mid,x,root<<1);
return fx(mid+1,r,x-(mid-l+1-sss),(root<<1)+1);
}
for(int i=1;i<=tt;i++)
{
int fff=fx(1,n,a[i],1,0);
upd(fff+n);
printf("%d ",fff);
}
树状数组:
#include<bits/stdc++.h>
using namespace std;
#define N 10000005
#define For(i,x,y)for(i=x;i<=y;i++)
#define Down(i,x,y)for(i=x;i>=y;i--)
#define Mems(i,j)memset(i,j,sizeof i)
#define lowbit(x)(x&-x)
const int LIM=1<<14;
bool bo[N];
int a[N],c[N],n;
void add(int x,int v)
{
while(x<=LIM)c[x]+=v,x+=lowbit(x);
}
int sum(int x)
{
int s=0;
while(x)s+=c[x],x-=lowbit(x);
return s;
}
int query(int x)
{
int i,y;
y=LIM;
//最大即2^14大于10000
for(i=y;i>>=1;)
//每次取一半(下界为y-2*i)
if(c[y-i]>=x)y-=i;
//如果中点大于等于(重点!不然会导致边界问题,因为我们返回的是y,一定要包含在区间内,自己思考一下),上界减小
else x-=c[y-i];
//如果太少,就减去当前的值继续做(下次下界会增加)
return y;
}
int main()
{
int m,i;
scanf("%d%d",&n,&m);
For(i,1,n)
{
scanf("%d",&a[i]);
add(a[i],1);
a[i]-=sum(a[i]-1)+1;
//变(“未知”)进制形式
}
/*next_permutation(a+1,a+n+1);*/
a[n]+=m;
Down(i,n,1)a[i-1]+=a[i]/(n-i+1),a[i]%=n-i+1;
//转化成合法状态
Mems(c,0);
bo[0]=1;
For(i,1,n+1)add(i,1),bo[i]=1;
For(i,1,n)
{
a[i]=query(a[i]+1);
printf("%d ",a[i]--);
//排除前a[i]个零,确定位置
if(bo[a[i]])add(a[i]+1,-1),bo[a[i]]=0;
}
return 0;
}
复杂度:
O ( n l o g n ) O(nlogn) O(nlogn)