题目连接:Large Sum Subarray
Large Sum Subarray
本题寻找以下配对的个数:$(i,j),i\leq j,\sum_{k=i}^ja_k>t$。通过求前缀和,我们可以把区间转换为两点的比较:$(i+1,j), S_j-S_{i}>t$。
联想一道经典的求逆序对的问题,题目求以下配对的个数:$(i,j),i\leq j, a_j>a_i$。本题稍加改变,也可以变为 $(i,j),i\leq j, a_j<a_i+t$。
归并排序的过程中(升序),在 merge 时,如果 $a_i+t$(第一部分) 比 $a_j$(第二部分) 小,那么 $a_i+t$ 比 $a_k,k>j$ 都要小。故我们把第一部分指针后移,并加上 $i$ 可以配对的数量 $R-j+1$,$R$ 是第二部分的右边界。
解决问后可以再想想,如果排序是降序的,求顺序对和求逆序对的代码又应该放在哪里?该如何证明正确性?
额外的注意事项
使用 C++ 的同学,读入时若使用 cin
需要加速,或使用 scanf
读入
参考代码
#include <bits/stdc++.h>
using namespace std;
#define n_max 1000000
int64_t t, ans;
int64_t a[n_max + 1], s[n_max + 1], tmp[n_max + 1];
void merge(int64_t* a, int left, int mid, int right) {
int i, j, k;
// count
i = left, j = mid + 1;
while (i <= mid && j <= right) {
if (a[i] + t < a[j]) {
i++;
// ordered pair counted here
ans += right - j + 1;
} else {
j++;
// inversion pair counted here
}
}
// merge
i = left, j = mid + 1, k = left;
while (i <= mid && j <= right) {
if (a[i] < a[j]) {
tmp[k++] = a[i++];
} else {
tmp[k++] = a[j++];
}
}
while (i <= mid) tmp[k++] = a[i++];
while (j <= right) tmp[k++] = a[j++];
for (int i = left; i <= right; ++i) a[i] = tmp[i];
}
void mergesort(int64_t* a, int left, int right) {
if (left >= right) return;
int mid = (left + right) / 2;
mergesort(a, left, mid);
mergesort(a, mid + 1, right);
merge(a, left, mid, right);
}
int main() {
// Read
int n;
scanf("%d%lld", &n, &t);
/*
Calculate s[i] to be the presum of a, then sum(a[i..j])=s[j] - s[i-1]
Problem (rephrase): find i<j, s[j] - s[i-1] > t
*/
s[0] = 0;
for (int i = 0; i < n; ++i) {
scanf("%lld", a + i);
s[i + 1] = a[i] + s[i];
}
// Solve
/*
Inspired by calculating inversion in array by mergesort (i<j, a[j]>a[i])
*/
mergesort(s, 0, n);
// Write
printf("%lld\n", ans);
return 0;
}