题意:有一个由1和-1组成的数列,现给M个询问(L,R),求区间(L,R)中和为0的最长子串长。
分块的思想,真的很神奇。
先考虑一个普通的询问,求一遍前缀和,记录下所有每个前缀和的所有下标,遍历一次,二分得结果,复杂度为nlgn。
如果预处理出一个区间,那么对于所有包含这个区间的询问,复杂度为两区间之差乘个log。那么,当预处理好的元区间足够小时,可以有很大的优化。设元区间大小为k,那么预处理的时间为n2/k,询问复杂度为Mklgn,这里我取k=n√。
#include<bits/stdc++.h>
using namespace std;
const int N = 6e5;
const int M = sqrt(N);
const int d = 5e4 + 10;
int n, m, x, y;
int a[N];
int unit, siz, ans[M][M];
vector<int> adds[N];
int vis[N];
int temp;
int main(){
while(~scanf("%d%d", &n, &m)){
for(int i = 0; i <= d * 2; i++)
adds[i].clear();
a[0] = d;
adds[d].push_back(0);
for(int i = 1; i <= n; i++){
scanf("%d", &a[i]);
a[i] += a[i - 1];
adds[a[i]].push_back(i);
}
unit = sqrt(n);
siz = (n + unit) / unit;
for(int i = 0; i <= n; i += unit){
memset(vis, -1, sizeof(vis));
temp = 0;
for(int j = i; j <= n; j++){
if(j % unit == 0) ans[i / unit][j / unit] = temp;//i到j - 1的解
if(vis[a[j]] == -1) vis[a[j]] = j;
else temp = max(temp, j - vis[a[j]]);
}
ans[i / unit][siz] = temp;
}
int res;
while(m--){
scanf("%d%d", &x, &y);//所求应是x-1到y的解
--x;
int l = (x + unit) / unit * unit;
int r = y / unit * unit;
res = ans[l / unit][r / unit];
for(int i = x; i < l; i++){
int pos = lower_bound(adds[a[i]].begin(), adds[a[i]].end(), y) - adds[a[i]].begin() - 1;
if(pos < 0) continue;
res = max(res, adds[a[i]][pos] - i);
}
for(int i = y; i >= r; i--){
int pos = lower_bound(adds[a[i]].begin(), adds[a[i]].end(), x) - adds[a[i]].begin();
if(pos == adds[a[i]].size()) continue;
res = max(res, i - adds[a[i]][pos]);
}
printf("%d\n", res);
}
}
return 0;
}