SPOJ-ZQUERY(分块)

题意:有一个由1和-1组成的数列,现给M个询问(L,R),求区间(L,R)中和为0的最长子串长。

分块的思想,真的很神奇。

先考虑一个普通的询问,求一遍前缀和,记录下所有每个前缀和的所有下标,遍历一次,二分得结果,复杂度为 nlgn

如果预处理出一个区间,那么对于所有包含这个区间的询问,复杂度为两区间之差乘个log。那么,当预处理好的元区间足够小时,可以有很大的优化。设元区间大小为k,那么预处理的时间为 n2/k ,询问复杂度为 Mklgn ,这里我取 k=n

#include

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;
}

你可能感兴趣的:(分块)