-题意
给定一个长度为n的序列,让你求满足:
i
<
j
i<j
i<j
a
[
i
]
∗
2
+
1
≤
a
[
j
]
a[i]*2+1 \leq a[j]
a[i]∗2+1≤a[j]的
(
i
,
j
)
(i,j)
(i,j)的个数
-做法
题目所给的条件显然是一个维护一维偏序关系的题目,类似于逆序数对的求法,我们可以用类似于归并排序的做法来维护它。
归并排序的过程中,可以考虑一个数组左半部分对右半部分的贡献,我们将左半数组记为a,右半数组记为b,如果a,b都是单调的数组的话就有非常好的性质,因为b是右半边数组,所以b数组的元素下标肯定是大于a数组的
这里可以看到b数组下标为j的地方对应的
a
[
i
]
∗
2
+
1
≤
b
[
j
]
a[i]*2+1\leq b[j]
a[i]∗2+1≤b[j]对应的最大的i如图所示,因为a,b两个数组我们假定都是单调的,当j增大的时候我们就不用每次从头枚举i,我们从上一个j所对应的i枚举就行,因为当j增大的时候
必然是如下图所示,我们所满足条件的i必然是要增大的。根据这个思想我们可以设定一个指针遍历st,st一开始指向左边数组的左端点,然后通过枚举j的位置(j的位置从右边数组的左端点开始),如果st指针满足
a
[
j
]
>
=
a
[
s
t
]
∗
2
+
1
a[j] >= a[st] * 2 + 1
a[j]>=a[st]∗2+1 且
s
t
<
=
m
i
d
st <= mid
st<=mid,我们就把st指针向右移动一格,知道不满足的时候,我们统计st和左端点的差,把这个差新加到我们的结果里,这个差就表明对于这个
j
j
j我们有多少个
i
i
i满足条件,这样下去,一次线性遍历我们就可以完成相关满足条件数据的统计,在完成这个过程以后,我们执行merge操作将两个有序数组合并,递归的不断执行下去就可以得到我们最后的答案。
-注意点
因为题中 n n n最大有1e6,我们结果可能会超过 i n t int int范围,需要用更大的 l o n g l o n g long long longlong变量
-核心代码
- 分治
void merge(int start, int end){
if (start < end){
int mid = (start + end) / 2;
merge(start, mid);
merge(mid + 1, end);
mergeit(start, end);
}
}
- 遍历数组统计个数
int st = start;
int mid = (start + end) / 2;
for (int j = mid + 1; j <= end; j++){
while (a[j] >= a[st] * 2 + 1 && st <= mid)
++st;
ans += (st-start);
}
- merge
int b1 = start, b2 = mid + 1, k = 0;
ll *tmp = (ll*)malloc((n+1)*sizeof(ll));
while (b1 <= mid&&b2 <= end){
if (a[b1] < a[b2])
tmp[k++] = a[b1++];
else
tmp[k++] = a[b2++];
}
while (b1 <= mid)
tmp[k++] = a[b1++];
while (b2 <= end)
tmp[k++] = a[b2++];
for (int i = 0; i < k; i++)
a[start + i] = tmp[i];
free(tmp);
-复杂度分析
- 遍历数组统计个数的时候前面已经提过是 O ( n ) O(n) O(n)的复杂度,因为用指针指向了 j j j所覆盖的最大 i i i的位置,不然每次从头开始遍历 i i i的复杂度是 O ( n 2 ) O(n^2) O(n2),这是我们无法接受的。
- merge操作合并两个有序数组的复杂度也是 O ( n ) O(n) O(n),这个课上宋老师已经讲过相应操作。
- 然后我们要对上述操作执行 l o g ( n ) log(n) log(n)次(分治思想,每次把一个大区间拆成两个小区间直到区间长度为2的时候开始合并)
- 所以总体复杂度为 O ( n l g n ) O(nlgn) O(nlgn)
-总结
核心部分还是用指针指向左端数组的元素使得我们能够线性遍历这个区间得到结果,这里大家可以思考一下如果题目改成 i < j i<j i<j a [ i ] ∗ 2 + 1 ≥ a [ j ] a[i]*2+1 \geq a[j] a[i]∗2+1≥a[j]我们该怎么处理这个指针呢?(和逆序数对的指针处理方式就基本一样了)