划分、解决、合并:分治法——《挑战程序设计竞赛》

4.6.1 数列上的分治法

给定一个1~n的排列,求这个数列中的逆序数对。

我们可以把一个大的数列A分成左右两个子数列B、C,那么数列A中所有的逆序对必定来自于以下三者其一:
(1) i,j都属于左子数列B的逆序对(i,j);
(2) i,j都属于右子数列C的逆序对(i,j);
(3) i属于B而j属于C

对于这(1)和(2),可以通过递归求得,对于(3),我们可以对数列C中的每个数字,统计数列B中比它大的数字的个数,再把结果加起来就好。代码如下,复杂度为$O(n\log n)。

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
typedef long long ll;
//输入
vector<int> A;
ll merge_count(vector<int> &a)
{
int n = a.size();
if(n<=1) return 0;
ll cnt = 0;
vector<int> b(a.begin(), a.begin()+n/2);
vector<int> c(a.begin() + n/2, a.end());

cnt += merge_count(b); //(1)
cnt += merge_count(c); //(2)
//此时b和c就已经分别排好序了
//(3)
int ai = 0, bi = 0, ci = 0;
while(ai < n)s
{
if(bi < b.size() && (ci == c.size() || b[bi] <= c[ci])) //ci == c.size()这个判断是,如果c数列已经全部找完了,剩下的全是b数列里的树,就不能看在有逆序数的存在
{
a[ai++] = b[bi++];
}
else
{
cnt += (n/2 -bi);
a[ai++] = c[ci++];
}
}
return cnt;
}
void solve()
{
printf("%lld\n", merge_count(A));
}

4.6.3 平面上的分治法