Poj P3468 A Simple Problem with Integers___分块/树状数组/线段树

题目大意:

给出 N N 个数 Ai A i Q Q 个 操 作 ,每次操作有 2 2 种方式,
①给出区间 [l,r] [ l , r ] 以及一个数 x x ,表示给 [l,r] 区 间 [ l , r ] 中的每个 Ai A i 都加上一个 x x
②给出 [l,r] 区 间 [ l , r ] ,回答区间 [l,r] [ l , r ] 的数的总和

1NQ105 1 ≤ N , Q ≤ 10 5
108Ai108 − 10 8 ≤ A i ≤ 10 8
104x104 − 10 4 ≤ x ≤ 10 4

分析:

这题其实有多种解决方法,
①分块:
我们将序列 A A 分成 N ⌊ N ⌋ 块,
每一块的长度都是 N ⌊ N ⌋ ,除了最后一块不确定。
这个显然。
我们设
Add[i]i A d d [ i ] 表 示 第 i 块 中 的 每 个 数 都 能 被 加 上 的 数
Sum[i]iAdd[i] S u m [ i ] 表 示 第 i 块 的 区 间 和 ( 不 包 括 A d d [ i ] )
Orz[j]jjiAdd[i] O r z [ j ] 表 示 第 j 个 数 增 加 的 数 ( 不 包 括 j 所 属 于 的 块 i 的 A d d [ i ] )
显然 Orz O r z 的初值就是序列 A A
Sum[i] S u m [ i ] 的 初 值 可 以 通 过 扫 一 遍 得 出
而后面的插入 x x 的操作,
就是在区间 [l,r] [ l , r ] 中 将他们之间能被完全覆盖的块的 Add[] A d d [ ] 加上 x x
lr 然 后 对 于 l 跟 r 所 归 属 的 块 单 独 操 作

[ll] [ l , l 所 属 块 的 右 端 点 ] 中的所有 Orz[] O r z [ ] 加上 x x
[rr] [ r 所 属 块 的 左 端 点 , r ] 中的所有 Orz[] O r z [ ] 加上 x x
此时对应的 Sum[i] S u m [ i ] 需 要 更 新
然后注意一下当 l,r l , r 在同一块的时候,
直接扫一遍 Orz[] O r z [ ] 更 新 即可
查询的时候也很简单,
[l,r] [ l , r ] 之间的全部块,直接 Add[]Sum[] 将 A d d [ ] 跟 S u m [ ] 计入答案即可
而对于头尾的块,扫一遍 Orz[] O r z [ ] ,且累加所属块的 Orz[] O r z [ ] 就可以了
同样要注意一下 l,r l , r 在同一个块的情况
总的思想就是“大段维护、局部朴素”
时间复杂度: O((N+Q)N) O ( ( N + Q ) ∗ N )

②树状数组:
在利用树状数组完成这题之前,
要先去解决用树状数组+差分实现的“区间增加+单点查询”到“单点增加+区间查询”的做法
题目链接:
https://www.luogu.org/problemnew/show/P3368

好了,我们设
d[] 当 前 的 差 分 数 组 是 d [ ]
ASum[] 当 前 的 序 列 A 的 前 缀 和 数 组 S u m [ ]
那么题目显然对于一个 [l,r] [ l , r ]
显然求的就是
Sum[r]Sum[l1] S u m [ r ] − S u m [ l − 1 ] + ri=lij=1d[j] ∑ i = l r ∑ j = 1 i d [ j ]
Sum[r]Sum[l1] S u m [ r ] − S u m [ l − 1 ] 是已知的,
我们考虑一下 ri=lij=1d[j] ∑ i = l r ∑ j = 1 i d [ j ]
显然可以对于一个
ri=1ij=1d[j] ∑ i = 1 r ∑ j = 1 i d [ j ] (i1l) ( 这 里 i 是 从 1 开 始 不 是 l )
可以转化成 ri=1(ri+1)d[i] ∑ i = 1 r ( r − i + 1 ) ∗ d [ i ]
进一步转化就是
r+1)ri=1d[i]ri=1id[i] ( r + 1 ) ∑ i = 1 r d [ i ] − ∑ i = 1 r i ∗ d [ i ]
那么我们就可以利用两个树状数组 S1,S2 S 1 , S 2 分别维护 d[i] d [ i ] id[i] i ∗ d [ i ] 的前缀和
每次的区间 [l,r] [ l , r ] 的修改,都可以变成对于差分数组 d[l]d[r+1] d [ l ] 和 d [ r + 1 ] 的修改,
d[l]xd[r+1]x d [ l ] 加 上 x , d [ r + 1 ] 减 去 x
那么我们所维护的前缀和中,
只需要在树状数组中稍微修改,即
S1lxr+1x S 1 的 位 置 l 加 上 x , 位 置 r + 1 减 去 x
S2llxr+1(r+1)x S 2 的 位 置 l 加 上 l ∗ x , 位 置 r + 1 减 去 ( r + 1 ) ∗ x

然后我们每次查询 [l,r] [ l , r ] 的 区 间 和 的 时 候
我们可以通过树状数组求出
ri=1d[i] ∑ i = 1 r d [ i ]
ri=1id[i] ∑ i = 1 r i ∗ d [ i ]
那么
(r+1)ri=1d[i]ri=1id[i] ( r + 1 ) ∑ i = 1 r d [ i ] − ∑ i = 1 r i ∗ d [ i ]
就是
ri=1ij=1d[j] ∑ i = 1 r ∑ j = 1 i d [ j ]

同理可以求出
li=1ij=1d[j] ∑ i = 1 l ∑ j = 1 i d [ j ]

他们的差就是
ri=lij=1d[j] ∑ i = l r ∑ j = 1 i d [ j ]
加上一开始的 Sum[r]Sum[l1] S u m [ r ] − S u m [ l − 1 ]
就可以实现查询了

时间复杂度: O((N+Q)logN) O ( ( N + Q ) l o g N )

③线段树:
这种方法,就很容易实现跟理解了,
自行百度吧,
注意一下这题要打Lazy标记即可

代码:

①分块:

#include
#include
#include
#include
#include
#define N 200005

using namespace std;

typedef long long LL;

struct Node {
    int l, r;
    LL Sum;
}Cp[505];
LL Belong[N], Add[N], Op[N], num;
int ans, n, m, tot;

void Insert(int l, int r) {
    int x = Belong[l], y = Belong[r];
    if (x == y) {
        for (int i = l; i <= r; i++) Op[i] += num;
        Cp[x].Sum += (r - l + 1) * num;
        return;
    }
    for (int i = x + 1; i <= y - 1; i++) Add[i] += num;
    for (int i = l; i <= Cp[x].r; i++) Op[i] += num;
    for (int i = Cp[y].l; i <= r; i++) Op[i] += num;
    Cp[x].Sum += (Cp[x].r - l + 1) * num;
    Cp[y].Sum += (r - Cp[y].l + 1) * num; 
}

LL Get_Sum(int l, int r) {
    int x =  Belong[l], y = Belong[r];
    LL Rp = 0;
    if (x == y) { 
        for (int i = l; i <= r; i++) Rp += Op[i];
        Rp += Add[x] * (r - l + 1);
    } else {
        for (int i = x + 1; i <= y - 1; i++) 
             Rp += Cp[i].Sum + Add[i] * (Cp[i].r - Cp[i].l + 1);
        for (int i = l; i <= Cp[x].r; i++) Rp += Op[i];
        for (int i = Cp[y].l; i <= r; i++) Rp += Op[i];
        Rp += Add[x] * (Cp[x].r - l + 1);
        Rp += Add[y] * (r - Cp[y].l + 1);
    }
    return Rp;
}

int main() {
    scanf("%d %d", &n, &m);
    int Rp = sqrt(n);
    tot = 0;
    for (int i = 1; i <= n; i++) {
         scanf("%lld", &num);
         if (i <= tot * Rp) Cp[tot].r = i; 
                       else tot++, Cp[tot].l = i;
         Cp[tot].Sum += num;   
         Belong[i] = tot;
         Op[i] = num;
    }
    Cp[tot].r = n;
    while (m--) {
         char str[2];
         int l, r;
         scanf("%s %d %d", str, &l, &r);
         if (str[0] == 'C') {
             scanf("%lld", &num);
             Insert(l, r);
         } else printf("%lld\n", Get_Sum(l, r));
    }
    return 0;
}

②树状数组:

#include
#include
#include
#include
#define N 100005

using namespace std;

typedef long long LL;

LL c[2][N], sum[N];
int n, m;

int lowbit(int x) {
    return x & (-x);
}

void Insert(int k, int x, int num) {
    while (x <= n) {
           c[k][x] += num;
           x += lowbit(x);
    }
}

LL Getsum(int k, int x){
    LL rp = 0;
    while (x) {
        rp += c[k][x];
        x -= lowbit(x);
    }
    return rp;
}

int main() {
    scanf("%d %d", &n, &m);
    for (int i = 1; i <= n; i++) {
         int x;
         scanf("%d", &x);
         sum[i] = sum[i - 1] + x;
    }
    while (m--) {
        char str[2];
        int l, r, x;
        scanf("%s %d %d", str, &l, &r);
        if (str[0] == 'C'){
            scanf("%d", &x);
            Insert(0, l, x);
            Insert(1, l, l * x);
            Insert(0, r + 1, - x);
            Insert(1, r + 1, - (r + 1) * x);
        } else {
            LL ans = (sum[r] - sum[l - 1]) + 
                     ((r + 1) * Getsum(0, r) - Getsum(1, r)) -
                     (l * Getsum(0, l - 1) - Getsum(1, l - 1));
            printf("%lld\n", ans);
        }
    }
    return 0;
}

③线段树:

#include
#include
#include
#include
#define N 500005

using namespace std;

typedef long long LL;

LL Tree[N], Lazy[N], num, ans;
int n, m;

void Insert(int W, int l, int r, int x, int y) {
    if (l == x && r == y) {
        Tree[W] += (r - l + 1) * num;
        Lazy[W] += num;
        return;
    }
    int mid = (l + r) >> 1;
    Tree[W * 2] += (mid - l + 1) * Lazy[W];
    Tree[W * 2 + 1] += (r - mid) * Lazy[W];
    Lazy[W * 2] += Lazy[W];
    Lazy[W * 2 + 1] += Lazy[W];
    Lazy[W] = 0;
    if (y <= mid) Insert(W * 2, l, mid, x, y);
             else if (x > mid) Insert(W * 2 + 1, mid + 1, r, x, y);
                          else {
                               Insert(W * 2, l, mid, x, mid);
                               Insert(W * 2 + 1, mid + 1, r, mid + 1, y);
                          }
    Tree[W] = Tree[W * 2] + Tree[W * 2 + 1];
}

void Count_Sum(int W, int l, int r, int x, int y) {
    if (l == x && r == y) {
        ans += Tree[W];
        return;
    }
    int mid = (l + r) >> 1;
    Tree[W * 2] += (mid - l + 1) * Lazy[W];
    Tree[W * 2 + 1] += (r - mid) * Lazy[W];
    Lazy[W * 2] += Lazy[W];
    Lazy[W * 2 + 1] += Lazy[W];
    Lazy[W] = 0;
    if (y <= mid) Count_Sum(W * 2, l, mid, x, y);
             else if (x > mid) Count_Sum(W * 2 + 1, mid + 1, r, x, y);
                          else {
                               Count_Sum(W * 2, l, mid, x, mid);
                               Count_Sum(W * 2 + 1, mid + 1, r, mid + 1, y);
                          }
}


int main() {
    scanf("%d %d", &n, &m);
    for (int i = 1; i <= n; i++) {
         scanf("%lld", &num);
         Insert(1, 1, n, i, i);
    }
    while (m--) {
        char str[2];
        int l, r;
        scanf("%s %d %d", str, &l, &r);
        if (str[0] == 'C') {
            scanf("%lld", &num);
            Insert(1, 1, n, l, r);
        } else if (str[0] == 'Q') {
            ans = 0;
            Count_Sum(1, 1, n, l, r);
            printf("%lld\n", ans);
        }
    }
    return 0;

你可能感兴趣的:(C++,树状数组,线段树,分块)