J-Subarray_2019牛客暑期多校训练营(第二场)
题意
有一个只由1,-1组成的数组,给出所有连续的1所在位置,求满足1的个数大于-1的个数的子区间的数量
题解
参考博客:https://www.cnblogs.com/Yinku/p/11221494.html
考虑做前缀和,问题就转化成sum[i]-sum[j] > 0的对数, 由于数据范围较大不可能对整个数组前缀和,可以只计算对答案有贡献的区间的前缀和,需要对给出的线段1进行合并,例如(111) -1 (11)就可以合并,(11) -1-1-1-1-1 (11)就没必要合并,这样可以去掉很多无用的-1区间,最后数组拆分成若干段,对每段分别计算区间数就行
由于sum[i]每次只会+1或-1, 可以利用lazy数组O(1)的求出大于sum[i]的前缀和的个数,具体实现看代码
代码
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
const int mx = 1e7+5;
const int INF = 0x3f3f3f3f;
int l[mx], r[mx], f[mx], g[mx];
int sum[mx*3], b[mx*3], lazy[mx*3];
int main() {
int n;
scanf("%d", &n);
for (int i = 1; i <= n; i++) scanf("%d%d", &l[i], &r[i]);
f[1] = r[1] - l[1] + 1;
for (int i = 2; i <= n; i++)
f[i] = max(0, f[i-1] - (l[i] - r[i-1] - 1)) + r[i] - l[i] + 1;
g[n] = r[n] - l[n] + 1;
for (int i = n-1; i >= 1; i--)
g[i] = max(0, g[i+1] - (l[i+1] - r[i] - 1)) + r[i] - l[i] + 1;
int base = 1e7, i = 1;
ll ans = 0;
while (i <= n) {
int j = i+1;
while (j <= n && f[j-1] + g[j] >= l[j] - r[j-1] - 1) j++;
j--;
int left = max(0, l[i] - g[i]), right = min((int)1e9, r[j]+f[j])-1;
int t = i, mi = INF, ma = 0;
sum[0] = 0;
for (int k = left; k <= right; k++) {
if (k >= l[t] && k <= r[t])
sum[k - left + 1] = sum[k - left] + 1;
else
sum[k - left + 1] = sum[k - left] - 1;
if (k == r[t]) t++;
mi = min(mi, sum[k - left + 1] + base);
ma = max(ma, sum[k - left + 1] + base);
b[sum[k - left + 1] + base]++;
}
for (int k = ma-1; k >= mi; k--) b[k] += b[k+1];
ans += b[base+1];
for (int k = left; k <= right; k++) {
t = sum[k - left + 1] + base;
b[t+1] -= lazy[t+1];
lazy[t] += lazy[t+1] + 1;
lazy[t+1] = 0;
ans += b[t+1];
}
for (int k = mi; k <= mx; k++) b[k] = 0, lazy[k] = 0;
i = j+1;
}
printf("%lld\n", ans);
return 0;
}