关于数论【康托展开及其逆运算】
表示这个东西背了很多次,但是次次忘,希望这次能够记住吧。
康托展开:
问45231是n=5的全排列中第几个排列?
ans:= 3*4! + 3*3! + 1*2! + 1*1! + 0*0! =93
这时求出的是在45231前面全部的排列,排名还要加1
所以对此的做法,就是将阶乘前面的求出来,这个就是在a[i]前面,还没出现过的数字。比如4前面1~3都没出现而1(或2或3)xxxx肯定在4xxxx前面,因为有四个不定的数字,所以乘上4!
逆运算:
问n=5的全排列中第94个是谁?94先-1
94/4!=3.875
所以第一个数字前面有3个数字
94先减3*4!=22
22/3!=3.6666666666666666666666666666667
前面还是有三个,因为4之前用过了,所以是5
由此类推。这个东西常用于状态压缩的。
模板题caioj1220:
#include<cstdio> #include<cstring> using namespace std; typedef long long LL; int n,a[20]; LL jc[20]; bool bo[20]; void kangtuo1() { jc[0]=1;for(int i=1;i<=n;i++) { scanf("%d",&a[i]); jc[i]=jc[i-1]*i; } LL ans=0;//有多少个这个全排列前面的 memset(bo,false,sizeof(bo));//这个有没有在前面出现过 for(int i=1;i<=n-1;i++) { int k=0;//k表示前面有多少个没被访问过的 for(int j=1;j<a[i];j++) if(bo[j]==false)k++; bo[a[i]]=true;//当前这个数被访问过 ans+=k*jc[n-i];//乘以当前个数的阶乘 } printf("%lld\n",ans+1); } void kangtuo2() { LL ans; scanf("%lld",&ans);ans--; memset(bo,false,sizeof(bo)); for(int i=1;i<=n;i++) { LL k=ans/jc[n-i];//有多少个比第i个位置小的数 ans-=k*jc[n-i]; for(int j=1;j<=n;j++) if(bo[j]==false) { if(k==0) { a[i]=j; bo[a[i]]=true; break; } k--; } } for(int i=1;i<n;i++)printf("%d ",a[i]); printf("%d\n",a[n]); } int main() { scanf("%d",&n); kangtuo1(); kangtuo2(); return 0; }
pain and happy in the cruel world.