3462. 【NOIP2013模拟联考5】休息(rest) (Standard IO)
Time Limits: 1000 ms Memory Limits: 262144 KB Detailed Limits
Goto ProblemSet做法:
View Code
一个并不明显的性质是,在对原序列进行第一次划分过后,以后
的每次划分得到的各个部分都恰好由两个数组成。 这是因为在第一次划分和
第一轮的翻转之后,原数列由若干个单调增序列拼接而成,形式如下:a1, a2…
ai, b1, b2…bj…z1…zk.考虑相邻两个部分,不妨设为 a 部分和 b 部分,其中 ai
和 b1前后相邻。若 ai<b1则可以合并成同一部分,否则会形成块(ai, b1),并
且在下一轮翻转,成为…ai-1, b1, ai, b2…。可以发现在这以后,由于有 ai-1<ai,
则 ai-1与 ai不会在同一个块里;同理 b1和 b2不会在同一个块里。即,任意连
续三个数必定不会是单调递减的。 那么,这以后每次翻转都只会将相邻的逆
序对交换。由于这个算法最终可以正确地得到结果,所以第一轮以后进行的
翻转操作数就等于第一轮之后序列的逆序对数。而第一轮的翻转数我们可以
直接模拟得到。 求一个序列的逆序对数的经典做法是归并排序,同时记录。
时间复杂度 O(nlog2n)
代码如下:
1 #include <cstdio> 2 #include <cstring> 3 #include <iostream> 4 #include <string> 5 #include <algorithm> 6 #include <cmath> 7 #define N 100007 8 #define LL long long 9 using namespace std; 10 int n, a[N], b[N], tot; 11 LL ans; 12 13 void cl(int l, int r) 14 { 15 for (int i = l; i >= r; i--) 16 a[++tot] = b[i]; 17 ans++; 18 } 19 20 inline void merge(int l, int mid, int r) 21 { 22 int i = l, j = mid + 1; 23 for (int k = l; k <= r; k++) 24 if (j > r || i <= mid && a[i] < a[j]) b[k] = a[i++]; 25 else b[k] = a[j++], ans += mid - i + 1; 26 for (int k = l; k <= r; k++) a[k] = b[k]; 27 } 28 29 inline void mergeSort(int a, int b) 30 { 31 int mid = (a + b) / 2; 32 if (a < b) 33 { 34 mergeSort(a, mid); 35 mergeSort(mid + 1, b); 36 merge(a, mid, b); 37 } 38 } 39 40 int main() 41 { 42 scanf("%d", &n); 43 for (int i = 1; i <= n; i++) 44 scanf("%d", &b[i]); 45 int i = 1, j = 1; 46 while (i <= n) 47 { 48 while (b[i] > b[i + 1] && i < n) i++; 49 cl(i, j); 50 j = i + 1; 51 i++; 52 } 53 memset(b, 0 ,sizeof(b)); 54 mergeSort(1, n); 55 cout << ans; 56 }