题解
这道题用stl的next_permutation可以妙解,但是如果不让用你能不能想到别的方法呢?
这道全排列让我想起了那道著名的 八数码 的题目,里面有一种用阶乘做hash的方法,非常牛逼。
那种方法的学名叫做康托展开,可以把全排列的各种排列与自然数 实现一一映射。
有这个武器,这题的思路就很明显了,把给出的排列 康托展开 得出映射数 再加上增加值 再做逆展开。
值得注意的是,这道题加的数非常小(<=100) 所以只用考虑后几位数的排列的序数。
ps: 为什么呢?观察可知,在做排列(往后有限次)的时候,前面的数字是固定的。
Code
// 省略头文件
using namespace std;
int n,m;
int fact[12];// 阶乘数
int cot[10001];
int re[15];// 重排列 计算
int cantor(int len){//对尾部len长做 康托展开
int ni,mark,k;
mark = 0;
k = len;
for(int i= n- (len-1); i<=n;i++){
ni=0;
for(int j=i+1;j<=n;j++)
if( cot[i]>cot[j] ) ni++;
mark += ni*fact[--k];
}
return mark+1;
}
void decan(int x,int len){ //对尾部len长做 逆展开
vector<int> v;
for(int i= n-(len-1);i<=n;i++)
v.push_back(cot[i]);
int r,t,k;
k=0;
for(int i=len; i>=1;i--){
r = x%fact[i-1];
t = x/fact[i-1];
x = r;
sort(v.begin(),v.end());
re[k++] = v[t];
v.erase(v.begin()+t);
}
}
int main(){
// 算阶乘
fact[0]=fact[1] = 1;
for(int i=2;i<=11;i++){
fact[i]=fact[i-1]*i;
}
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>cot[i];
}
// 算末尾几位 可以容纳加数
int pos,ilen=1;
while(fact[ilen]<= m) ilen++;
pos = cantor(ilen);
while( fact[ilen]-pos<m ){ ilen++;// fact[len] >= cantor(len) + m 时此长度可行
pos = cantor(ilen);
}
decan(pos+m-1,ilen);// 逆康托 结果存于re[]
int k=0;
for(int i=1;i<=n;i++){
if(i>= (n-ilen+1)) cout<<re[k++]<<" ";
else cout<< cot[i]<<" ";
}
return 0;
}