hdu1027(逆康托展开)
src:http://acm.hdu.edu.cn/showproblem.php?pid=1027
一开始已经提过了,康托展开是一个全排列到一个自然数的双射,因此是可逆的。即对于上述例子,在(1,2,3,4,5)给出61可以算出起排列组合为 34152。由上述的计算过程可以容易的逆推回来,具体过程如下:
- 用 61 / 4! = 2余13,说明a[5]=2,说明比首位小的数有2个,所以首位为3。
- 用 13 / 3! = 2余1,说明a[4]=2,说明在第二位之后小于第二位的数有2个,所以第二位为4。
- 用 1 / 2! = 0余1,说明a[3]=0,说明在第三位之后没有小于第三位的数,所以第三位为1。
- 用 1 / 1! = 1余0,说明a[2]=1,说明在第二位之后小于第四位的数有1个,所以第四位为5。
- 最后一位自然就是剩下的数2啦。
- 通过以上分析,所求排列组合为 34152。
!!!用61/4!不用担心受到3!这一项的影响,因为这一样的值不可能>=4!,因为3!这一项的系数表示第四位前面小于第四位的,易知这个值<=3 !!!
注意:cantors算法给出的序列全排列顺序的下标是从0开始的,所以从题目输入序号后要减一!!!
ac代码:
#include<bits/stdc++.h> using namespace std; #define per(i,a,b) for(int i=a;i <= b;i++) #define max(a,b) a=max(a,b) #define min(a,b) a=min(a,b) #define sz(x) (int)x.size() typedef long long ll; ll gcd(ll a,ll b){while(b){ll t=b;b=a%b;a=t;}return a;} const int inf=0x3f3f3f3f; const int mod=1000000007; #define siz 40005 static const int FAC[] = {1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880}; // 阶乘 int n,m; vector<int>v; void decantor() { vector<int>chs;//存放可选数 for(int i=1;i<=n;i++)chs.push_back(i); int cnt=n; while(cnt>0){ if(cnt<=8){ int r=m/FAC[cnt-1]; m%=FAC[cnt-1]; v.push_back(chs[r]); chs.erase(chs.begin()+r); } else { v.push_back(chs.front()); chs.erase(chs.begin()); } cnt--; } } int main() { #ifndef ONLINE_JUDGE freopen("b\\Data_In.txt","r",stdin); #endif while(scanf("%d%d",&n,&m)!=EOF){ m--; v.clear(); decantor(); for(int i=0;i<v.size();i++)printf("%d%c",v[i]," \n"[i!=(v.size()-1)?0:1]); } return 0; }