题目来源:https://acm.sjtu.edu.cn/OnlineJudge/problem/1219
1219. 重要的逆序数对
题目描述
对于一个给定n个数的序列a1,a2,⋯,ann ,定义一个数对(ai,aj)(ai,aj)称为逆序对当且仅当i<j⋀ai>aj
可是人们感觉这个度量可能太敏感了,因此人们又定义一个数对 (ai,aj)(ai,aj)称为重要的逆序对当且仅当 i<j⋀ai>2aj.
你的任务就是找出给定序列的重要的逆序对数目。
输入
第1行:一个数n,表示序列的长度,
第2-n+1行,每行一个数,表示序列从前到后的每个数 a1,a2,⋯,an
输出
第1行:一个数M,表示给定序列的重要的逆序对数目。
Sample input
5
9
3
5
3
1
Sample output
6
Limits
60%:N <= 5000 100%: N <= 200000
Time limits
1000ms
------------------------------------------------------------
思路
这题也是2018年北大信科夏令营机试E题,当时考试的时候弄了好久都没弄对,考完偶然看见,重做一遍一下子就AC了,唉……
思路与“普通逆序对”(i<j⋀ai>aj)的思路完全一致,也是在归并排序的归并过程中计算逆序对个数。与普通逆序对不同的是,由于普通逆序对的判定条件(“前面的数比后面的数大”)与归并排序的归并条件相同,故可以在归并过程中同时计算普通逆序对;而重要逆序对的判定条件(“前面的数是后面的两倍”)比归并条件(“前面的数比后面的数大”)更严格,因此一方面保证了可以利用归并排序归并过程中前后两个数组的有序性计算重要逆序对(判定条件与归并条件相同或更严格就可以利用归并过程做判定),另一方面也导致需要在归并前另写一个循环单独计算重要逆序对。
------------------------------------------------------------
代码
#include<cstdio>
#include<cstring>
const int NMAX = 200005;
int n;
int a[NMAX] = {};
int b[NMAX] = {};
long long cnt = 0;
void merge(int l, int m, int r)
{
int i = l, j = m, k = l, times = 0;
memcpy(b+l, a+l, (r-l)*sizeof(int)); // 复制内存
// 单独用一个while循环计算“重要逆序对”,计算方法与逆序对基本相同
while (i < m && j < r)
{
if (b[i] > 2 * b[j]) // 计算普通逆序对时这里是"if (b[i] > b[j])",其余都一样
{
cnt += j - i - times;
times++;
j++;
}
else
{
i++;
}
}
// 归并过程(计算普通逆序对时计算过程嵌入在归并过程中)
i = l, j = m;
while (i < m && j < r)
{
if (b[i] < b[j])
{
a[k++] = b[i++];
}
else
{
a[k++] = b[j++];
}
}
while (i < m)
{
a[k++] = b[i++];
}
while (j < r)
{
a[k++] = b[j++];
}
}
void merge_sort(int l, int r)
{
if (r - l == 1)
{
return;
}
int m = (l + r) / 2;
merge_sort(l, m);
merge_sort(m, r);
merge(l, m, r);
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("sjoj1219.txt", "r", stdin);
#endif
scanf("%d", &n);
int i = 0;
for (i=0; i<n; i++)
{
scanf("%d", a+i);
}
merge_sort(0, n);
printf("%lld", cnt);
return 0;
}