终于看到了完结的曙光。。话说我规划今天做后面的题诶。。
k大数
随机选取一个数,将比它大的放在左边,小的放在右边,设有
cnt
c
n
t
个比它小的,
k≤cnt
k
≤
c
n
t
就在左半段找,否则去右半段。这样递归即可,复杂度
O(n)
O
(
n
)
。
万能的STL有nth_element()
分治排序与逆序对
#include <iostream>
using namespacestd;
int n, s, a[100005], t[100005], i;
void mergesort(int l, int r)
{
if (l == r)
return;
int mid = (l + r) / 2;
int p = l;
int i = l;
int j = mid + 1;
mergesort(l, mid);
mergesort(mid + 1, r);
while (i <= mid && j <= r)
{
if (a[j] < a[i])
{
s += mid – i + 1;
t[p] = a[j];
p++;
j++;
}
else
{
t[p] = a[i];
p++;
i++;
}
}
while (i <= mid)
{
t[p] = a[i];
p++;
i++;
}
while (j <= r)
{
t[p] = a[j];
p++;
j++;
}
for (i = l; i <= r; i++)
a[i] = t[i];
}
int main()
{
cin >> n;
for (i = 1; i <= n; i++)
cin >> a[i];
mergesort(1, n);
cout << s << endl;
return 0;
}
觉不觉得这个代码有点熟悉。。
其实这个代码是2017初赛程序填空T3的代码。。原封不动的拿过来了。。
原理这里稍微解释一下吧,重点是merge的部分。
首先我们考虑一下,如果不进行求逆序对,那么我们的过程是怎样的。其实很简单,我们建两个指针然后滚一遍,碰到下一个数就比较大小。显而易见的这个时候我们的两个子数组是已经排序完成的,所以直接维护即可。当然如果到最后发现还有一个数组有剩余,就直接放在最后。
那么我们考虑哪些地方对逆序对有贡献。显而易见的,一个是子数组内部,一个是两个数组之间。但是我们知道,对于一个递归过程而言,子数组内部的一定已经处理完毕了,所以只需要考虑两个子数组之间的贡献,那做法就很显然了,如果第二个数组的元素比第一个数组的某个元素要小,就意味着这个元素比第一个数组剩余元素都要小,所以我们加上 mid−i+1 m i d − i + 1 个元素。
当然顺序对也是同理的,我们考虑第一个数组对第二个数组的贡献即可。这样的复杂度是 O(nlogn) O ( n l o g n ) 的。
模板题:POJ2299,记得开LL。。
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
#define N 500005
int a[N], c[N], t;
long long cnt;
void merge(int l, int r) {
if(l == r) return;
int mid = l + r >> 1;
merge(l, mid);
merge(mid + 1, r);
int i = l, j = mid + 1, p = l;
while(i <= mid && j <= r) {
if(a[i] > a[j]) {
cnt += mid - i + 1;
c[p] = a[j];
++j; ++p;
} else {
c[p] = a[i];
++i; ++p;
}
}
if(i <= mid)
for(int k = i; k <= mid; ++k)
c[p++] = a[k];
if(j <= r)
for(int k = j; k <= r; ++k)
c[p++] = a[k];
for(int k = l; k <= r; ++k) a[k] = c[k];
}
int main() {
int n;
while(~scanf("%d", &n)) {
if(n == 0) break;
for(int i = 1; i <= n; ++i) scanf("%d", &a[i]);
merge(1, n);
printf("%lld\n", cnt);
cnt = 0;
}
return 0;
}
奇数码先打lazytag了。。。
新blog:http://lire.yuyuko.cc ,不过似乎不是很稳定的样子qwq