康托展开&逆康托展开学习笔记
啊。。。好久没写了。。。可能是最后一篇学习笔记了吧
题目大意:给定序列求其在全排列中的排名&&给定排名求排列。
这就是康托展开&&逆康托展开要干的事了。下面依次介绍
一、康托展开
首先,知道它是干嘛的。
就是给定一个全排列之中的序列,求其在整个全排列中的排名。
给出式子:
$k=sum_{i=1}^n(n-i)!\sum_{j=i+1}^n(a_{k,i}>a_{k,j})$
解释一下:考虑这个序列的第i位,对于这个序列,只有前i位都小于等于它,第i位一定小于它的所有序列才会在它前面,于是对每一位考虑组合,就是这个结果了。
代码片:
ll ktz(ll *a) { ll ans=0; for(ll i=1;i<=n;i++) { ll cnt=0; for(ll j=i+1;j<=n;j++) { if(a[i]>a[j])//对每一位考虑 cnt++; } ans+=cnt*fac[n-i]; } return ans+1;//因为求的是前有多少,所有排名+1 }
二、逆康托展开
好了,那有了排名怎么求数组呢?
由上述康托展开可得,要得到数组的每一位,就必须确定前面有多少比它大的。
于是反过来,对每一位考虑可以由多少比它大的,也就是求上述式子中括号里的东西,然后一位一位还原,就成了原序列
过程:首先,同上,-1
然后对每一位,把序号除以对应的fac,确定一个没用过的数,作为当前的答案即可
代码片:
ll nkt(ll k) { k-=1; ll j; memset(vis,0,sizeof(vis)); for(ll i=1;i<=n;i++) { ll s=k/fac[n-i]; for(j=1;j<=n;j++) { if(!vis[j]) { if(!s) break; s--; } } printf("%d ",j); vis[j]=1; k%=fac[n-i]; } printf("\n"); }
(完)