[学习笔记]Cantor展开/CF501D

Cantor展开

这是大概半年前学到的trick,今天突然想起来就来复习一下。

我们知道对于n个数1,2,,n的排列一共有n!个,同时我们很容易定义两个不同排列p1,p2的大小关系——按字典序比较就行,于是我们很自然地会发现,对于任意一个排列P=(p1,p2,,pn),应该是能够和自然数建立一一对应的关系的——依靠这个排列在所有排列里字典序的排名。

Cantor展开就是做这件事情,我们定义最小的排列1,2,3,4,5的排名是0,接着往下1,2,3,5,4的排名就是1,Cantor展开的值也就是1。

举个栗子

对于任意一个排列P我们也容易算出他的排名:

4,3,5,1,2为例,第一个元素是4意味着以1,2,3为开头的排列的排名都一定比他小,这样的排列有3×4!个,同样,如果定下第一个元素是4,对于前两个元素是(4,3),意味着(4,1),(4,2)为开头的排列的排名比他小,这样的排列有2×3!个,以此类推,定下前两个元素,对于前三个元素是(4,3,5)的排列,意味着以(4,3,1),(4,3,2)为开头的元素排名比他小,这样的排列又有2×2!个,接着就是(4,3,5,1)为开头,以(4,3,5)为开头的排列没有比他小的,所以最后比当前排列小的排列的个数就是:3×4!+2×3!+2×2!=88个,这也就是这个排列的排名/Cantor展开的值。

实现

容易发现,一个排列P=(p1,p2,,pn)的Cantor展开X=i=1nai(ni)!,其中ai表示有多少个t使得(p1,,pi1,t)>(p1,,pi)这个排列比原排列P来的大,因为前面用过的已经不能用了,所以这个t的个数其实就是在pi+1,,pn里比pi小的数的个数,这可以用一个树状数组来维护,于是求一个排列Cantor展开的系数就可以做到O(nlogn)的复杂度啦。

一些事项

Cantor展开其实是一个变进制数,在一些地方似乎能用得上(好像有见过)。

Cantor展开的值虽然很大,不过感觉更多时候是算系数来解决一些问题。

逆过程

都说是一一对应关系,那一个自然数也应该要能映射回去对吧?首先一个具体的数字转换成n个系数的表达方式很好做:按照上面的定义,很明显有aini,对于t=0,1,,k1,有k!>t(k1)!,所以如果我们只有一个X,n,每次求ai的时候,ai=X/(ni)!,再用Xmod(ni)!来更新X就行。

接下来就是要怎么通过系数求出具体的排列了,依然用前面的栗子,(4,3,5,1,2)生成的Cantor展开的系数应该是(3,2,2,0,0),第一个3意味着{1,2,3,4,5}里有3个比第一个元素小的,那么第一个元素就是4,接着我们定下来4,再从{1,2,3,5}里选排名=2+1=3的3,第二个元素就是3,接着往下,从{1,2,5}里选排名第三小的,也就是5,接着是{1,2}里选排名第0+1=1的1…

一个比较简单的实现就还是一个树状数组:值域树状数组,一开始全部设为1,每次二分找到最小的前缀和超过ai的位置,就是这个位置上的答案啦,这样做是O(nlog2n)的,如果要优化掉这个log那就是直接在线段树上二分啦。

CF501D

给两个排列p,qOrd(p)表示一个排列p的排名,求排名为(Ord(p)+Ord(q))mod(n!)的排列。

就是一个裸的Cantor展开啦:

rep(i,1,n)modify(i,1);
rep(i,1,n){
f[i]=query(a[i]);
modify(a[i]+1,-1);
}
rep(i,1,n)modify(i,1);
rep(i,1,n){
f[i]+=query(b[i]);
modify(b[i]+1,-1);
}
for(int i=n;i>=1;i--)if(f[i]>n-i){
f[i-1]++;
f[i]%=n-i+1;
}
rep(i,1,n)modify(i,1);
rep(i,1,n){
int l=1,r=n,ret=-1;
while(l<=r){
int mid=(l+r)>>1;
int q=query(mid);
if(q<f[i]+1)l=mid+1;
else{
r=mid-1;
ret=mid;
}
}
modify(ret,-1);
printf("%d ",ret-1);
}
posted @   yoshinow2001  阅读(82)  评论(0编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
点击右上角即可分享
微信分享提示