分块算法主要用于给定序列的区间询问问题,能够以较小的时间代价暴力求解,时间复杂度一般在O(n*n^0.5)。关键在O(1)维护好某一区间在增加或者减少一个边界元素所带来的影响。需要注意的就是在更新的区间的时候要先放大在缩小,否则可能出现当前区间左右边界互换的情况,这个影响某一些题可能没有影响,但是极有可能出错。

时间复杂度:先考虑左边界的时间复杂度,由于分成了sqrt(n)块,而同一块中左标移动的范围最多是sqrt(n),那相邻块跳转的情况呢?可以虚拟出每块中有至少一个询问进行思考,那么相邻块之间的移动次数最大为2*sqrt(n)。由于共有Q次询问,因此最终时间复杂度为O(Q*sqrt(n))。再考虑右边界,对于同一块内的右边界来说,其值是单调递增的,因此时间复杂度为O(n),相邻块跳转为O(2*n),由于共有sqrt(n)块,因此最终时间复杂度为O(n*sqrt(n))。

CF86D Powerful array

题意:给定一N个元素的序列,序列长度最多为200000,现在定义若一个数s在区间[L, R]出现Ks次,那么这个串的价值就加上Ks^2*s。例如序列3,3,5,6,5,5,那么这个串的权值为:2^2*3+3^2*5+1^1*6。现有Q(与N同)次询问,每次询问都是[L, R],要出输出该子串的权值。

分析:离线处理,将所有的询问排序,第一关键字按照左端点所在块排序,第二关键字按照右端点排序。在确保能够O(1)在某一区间扩大/缩小一单位维护出答案的情况下,这样能够保证在O(Q*n^0.5+n*n^0.5)的时间复杂度计算出来。至于O(1)的处理,只需要考虑 Ks^2*s 和 (Ks-1)^2*s 的关系即可。

#include <cstdlib>
#include <cstring>
#include <cstdio>
#include <cmath>
#include <algorithm>
using namespace std;

typedef long long LL;
struct Node {
    int l, r, b, No;
    bool operator < (const Node &t) const {
        if (b != t.b) return b < t.b;
        return r < t.r;    
    }
};
const int N = 200005;
const int M = 1000005;
int n, Q;
int seq[N];
Node q[N];
int cnt[M]; // 统计每个数出现了多少次,如果范围过大可以考虑离散化 
LL ans[N];

LL & cal(int x, int &l, int &r, LL &ret) {
    int L = q[x].l, R = q[x].r;
    while (l > L) {
        --l;
        int t = seq[l];
        ret += 2LL*cnt[t]*t+t;
        cnt[t]++;
    }
    while (r < R) {
        ++r;
        int t = seq[r];
        ret += 2LL*cnt[t]*t+t;
        cnt[t]++;
    }
    while (l < L) {
        int t = seq[l];
        ret += -2LL*cnt[t]*t+t;
        ++l;
        cnt[t]--;
    }
    while (r > R) {
        int t = seq[r];
        ret += -2LL*cnt[t]*t+t;
        --r;
        cnt[t]--;
    }
    return ret;
}

void solve() {
    int B = (int)sqrt(1.0*n);
    memset(cnt, 0, sizeof (cnt));
    for (int i = 1; i <= Q; ++i) {
        scanf("%d %d", &q[i].l, &q[i].r);
        q[i].b = q[i].l / B;
        q[i].No = i;
    }
    sort(q+1, q+1+Q);
    int l = q[1].l, r = q[1].r;
    LL ret = 0;
    for (int i = l; i <= r; ++i) {
        int t = seq[i];
        ret += 2LL*cnt[t]*t+t;
        cnt[t]++;
    }
    ans[q[1].No] = ret;
    for (int i = 2; i <= Q; ++i) {
        ans[q[i].No] = cal(i, l, r, ret);
    }
    for (int i = 1; i <= Q; ++i) {
        printf("%I64d\n", ans[i]);
    }
}

int main() {
    while (scanf("%d %d", &n, &Q) != EOF) {
        for (int i = 1; i <= n; ++i) {
            scanf("%d", &seq[i]);
        }
        solve();
    }
    return 0;
}
View Code

相关文章: