Uim的情人节礼物·其之壱
题目描述
情人节到了,Uim打算给他的后宫们准备情人节礼物。UIm一共有N(1<=N<=9)个后宫妹子(现充去死 挫骨扬灰!)。
为了维护他的后宫的稳定。他通过编程,得出了一个送礼物的最佳顺序。这个我们管不着。
然而他认为,如果什么事情做得太圆满不是什么好事。于是他希望得到 原定顺序 的 前一个字典序的序列。
输入格式
第一行一个整数N
第二行N个整数,表示原定排列
输出格式
前一个排列
样例 #1
样例输入 #1
3
1 3 2
样例输出 #1
1 2 3
提示
若当前排列已经是第一个,则输出’ERROR’(引号不输出)
Solution
1.模拟
2.prev_permutation函数
3.康泰展开与逆康托展开
Code
1.模拟
bool cmp(int a,int b){
return a > b;
}
int k = n;
while(k != 0 && a[k] >= a[k - 1]) k --; //找到第一个左边的数比自己小的数
if(!k){ //如果找不到,说明已经是最小字典序
puts("ERROR");
return 0;
}
k -= 1; //标记位置
int p = lower_bound(a + j + 1,a + n + 1,a[j] - 1) - a; //二分搜素j后面找到比a[j]小1的数
swap(a[j], a[p]); //交换他们的位置
sort(a + j + 1, a + n + 1, cmp); //将a[j]后面的数从大到小排序
for(int i = 1; i <= n ; i ++ )cout << a[i] << " ";
举个例子:
652134 的前一个字典序为651432,从第1位找到第6位,可以发现找到第3位两式出现不同,前一个字典序第3位后面的数按照从大到小排序。
一般地:
x 1 x 2 … x k x k+1 x k+2 x k+3…x n 前字典序为x 1 x 2 … y k y k+1 y k+2 y k+3…y n
y k = x k - 1
y k+1 y k+2 y k+3…y n为x k x k+1 x k+2 x k+3…x n除去y k 后的逆序
2.prev_permutation函数
prev_permutation函数可以制造前一个排列,如果已经为第一个,则返回false
if(prev_permutation(a,a+n)) //如果为真就输出数组
for(int i=0;i<n;i++)
cout<<a[i]<<" ";
else cout<<"ERROR"; //否则输出ERROR
3.康托展开与逆康托展开
康托展开
是一种类似于hash的做法,是一个全排列到一个自然数的双射,常用于构建hash表时的空间压缩
简单来说,就是用把数列映射为一个数,一个数来表示一个排列,而这个数实际上就是当前排列在所有排列中的排名。
逆康托展开就是要把自然数映射回排列
#include<bits/stdc++.h>
using namespace std;
typedef vector<int> VI;
int n,a[15],f[15];
VI v;
int cantor(){
int ans = 0;
for(int i = 1; i <= n; i ++ ){
int cnt = 0;
for(int j = i + 1; j <= n; j ++ ){
if(a[i] > a[j])cnt ++; //记录下i的右边有几个比i小的数
}
ans += cnt * f[n - i]; //固定1-(i-1),i-n共有cnt * f[n - i]种情况
}
return ans;
}
void incantor(int k){
for(int i = 1; i <= n; i ++ )v.push_back(i); //预处理
for(int i = 1; i <= n - 1; i ++ ){
a[i] = v[k / f[n - i]]; //根据i的右边有几个比i小的数算出第i个数是什么
v.erase(v.begin() + k / f[n - i]); //删除已固定的数
k %= f[n - i];
}
a[n] = v[0];
}
int main(){
cin >> n;
f[1] = 1;
for(int i = 2; i <= 9; i ++)f[i] = f[i - 1] * i; //预处理出阶乘
for(int i = 1; i <= n ; i ++ )cin >> a[i];
incantor(cantor() - 1);
for(int i = 1; i <= n ; i ++ )cout << a[i] << " ";
}