赞
踩
题目的基本意思是:给定一个数字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; }
- 1
- 2
- 3
方式二:
int C(int n){ int ans = 0; for(int i = 1;i < n;i++){ ans += i; } return ans; }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
利用求最大逆序对,我们可以求出数组中有多少个数,即确定了结果中数字的个数。
如: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++; } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
// 混乱的数组 #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; }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。