混乱的数组
题目:
思路:
题目的基本意思是:给定一个数字x,找出一个数组,要求数组中有x对下标小的数其值更大的数对。我们可以利用逆序对来进行求解,如:3, 2, 1其逆序对分别为:2, 1, 0因此数组3, 2, 1的逆序对之和为3。
我们知道,逆序排序的序列的逆序对最大,并且逆序排列的序列求的逆序对的公式为: n × ( n − 1 ) 2 \frac{n\times(n - 1)}{2} 2n×(n−1),其实这也是一个组合问题:如3, 2, 1其逆序可以这样思考:从这个序列中任意选择两个数,并且要求不重复,有:32,31,21这三个,可能会有人有这样的疑惑,如果是23,13,12是不是就不满足题目要求了?要知道,我们求的只是其组合个数,不用管其排列顺序,可以设定默认是按照从大到小的进行排列的。
求最大逆序对的程序
方式一:
int C(int n){ return (n * (n - 1)) / 2; }
方式二:
int C(int n){ int ans = 0; for(int i = 1;i < n;i++){ ans += i; } return ans; }
利用求最大逆序对,我们可以求出数组中有多少个数,即确定了结果中数字的个数。
如:7个数的最大逆序为:21,8个数的最大逆序为28
如果我们要求一个数的逆序为25,那么便确定了结果一共有8个数字,如:[8, 7, 6, 5, 1, 3, 2 ,1]
(题目要求只包含正整数)
显然这个结果满足逆序为25,但是不满足题目要求:最小字典序,我们可以继续进行优化,最终得到:[5, 4, 3, 3, 2, 2, 1, 1]
,满足最小字典序:
逆序对 | 满足逆序对的最小字典序列 |
---|---|
22 | 265432 11 |
23 | 3543 2211 |
24 | 44332211 |
25 | 54 332211 |
26 | 6543 2211 |
27 | 765432 11 |
28 | 87654321 |
通过观察以上的表格,我们可以观察出以下规律:
如果给出的逆序对大于等于 ( C n 2 + C n − 1 2 ) 2 \frac{(C_n^2 + C_{n - 1}^2)}{2} 2(Cn2+Cn−12),那么满足以下规律,斜体部分都是严格递减的,我们可以从后向前进行填充,先填充成对出现的(填充的个数为 C n 2 − x C_n^2 - x Cn2−x个,x为需要求的逆序对的个数,即题目给出的x),并且使用一个变量用于计数,如果没有填充完毕,就进行严格的单调递增的进行填充,如果填充的长度满足求出的长度就结束填充,最后逆向输出即可。(如:逆序对为25,那么需要填充$C_8^2 - 25 = 28 - 25 = 3 $个成对出现的数字,然后再进行单调递增的填充4,5,6最后逆向输出即可)
如果给出的逆序对小于 ( C n 2 + C n − 1 2 ) 2 \frac{(C_n^2 + C_{n - 1}^2)}{2} 2(Cn2+Cn−12),满足以下规律:依然是从后向前进行填充,其中重复数字部分填充 x − C n − 1 2 x - C_{n - 1}^2 x−Cn−12次,填充完毕之后进行严格单调的填充,不填充最后一个数字,最后一个数字与 ( x − C n − 1 2 ) × 2 (x-C_{n - 1}^2) \times 2 (x−Cn−12)×2个位置的值相等,即表格中加粗的部分的数字。(如:逆序对为23,那么需要填充 23 − C 7 2 = 2 23 - C_7^2 = 2 23−C72=2次重复的数字,然后单调递增的进行填充到总长度-1个位置,最后一个位置的值等于 ( 24 − C 7 2 ) × 2 = ( 24 − 21 ) × 2 = 2 × 2 = 4 (24-C_7^2) \times 2 = (24 - 21) \times 2 = 2 \times 2 = 4 (24−C72)×2=(24−21)×2=2×2=4位置的值,实际上是第5个位置,因为数组的下标是从0开始的)
部分代码实现
// i表示已经找到的数组的长度 int oi = C(i - 1); int cut = (C(i) + oi) / 2; // 中间的情况 if(l < cut){ // 短填充 int tempc = l - C(i - 1); int idx = tempc * 2; int num = 1; int numlen = 0; while(numlen < i - 1){ if(tempc > 0){ ans.push_back(num); ans.push_back(num); tempc--; num ++; numlen += 2; }else{ ans.push_back(num); num++; numlen += 1; } } ans.push_back(ans[idx]); // 最后一位数字 }else{ // 长填充 int tempc = C(i) - l ; int num = 1; int numlen = 0; while(numlen < i){ if(tempc > 0){ ans.push_back(num); ans.push_back(num); tempc--; numlen += 2; }else{ ans.push_back(num); numlen += 1; } num++; } }
代码:
// 混乱的数组
#include<iostream>
using namespace std;
#include<vector>
// 求C_n^2 如果是选择2个数进行组合,数学公式(n * (n - 1)) / 2
int C(int n){
return (n * (n - 1)) / 2;
}
//int C(int n){
// int ans = 0;
// for(int i = 1;i < n;i++){
// ans += i;
// }
// return ans;
//}
void solve(){
int l = 0;
cin>>l;
// cout<<C(5)<<endl;
int i = 2;
bool flag = false;
while(true){
if(C(i) == l){
flag = true;
break;
}else if(C(i) > l){
break;
}else{
i++;
}
}
cout<<i<<endl;
if(flag){ // 表示找到了数组
for(int k = i;k > 0;k --) cout<<k<<' ';
}else{
vector<int> ans;
int oi = C(i - 1);
int cut = (C(i) + oi) / 2; // 中间的情况
if(l < cut){
// 短填充
int tempc = l - C(i - 1);
int idx = tempc * 2;
int num = 1;
int numlen = 0;
while(numlen < i - 1){
if(tempc > 0){
ans.push_back(num);
ans.push_back(num);
tempc--;
num ++;
numlen += 2;
}else{
ans.push_back(num);
num++;
numlen += 1;
}
}
ans.push_back(ans[idx]); // 最后一位数字
}else{
// 长填充
int tempc = C(i) - l ;
int num = 1;
int numlen = 0;
while(numlen < i){
if(tempc > 0){
ans.push_back(num);
ans.push_back(num);
tempc--;
numlen += 2;
}else{
ans.push_back(num);
numlen += 1;
}
num++;
}
}
//进行逆序输出结果数组
for(int i = ans.size() - 1;i >= 0;i --){
cout<<ans[i]<<' ';
}
}
return ;
}
int main(){
int t = 1;
while(t--){
solve();
}
return 0;
}