• 分块-区间求和


    一:分块

      分块的思想就是通过合适的划分,将一部分信息预处理并保存下来,用空间来换取时间,其实分块是“优化”的暴力,效率比不上树状数组和线段树,但它更通用和容易实现。

    二:例题1

      给定一个长度为N(N ≤ 10^5)的数列A,然后有M(M ≤ 10^5)个操作指令。

      操作1:格式:1 x y k 含义:将区间[x, y]上的每一个数都加上k

      操作2:格式:2 x y   含义:输出区间[x, y]内每个数的和

      Luogu P3372 

      这道题用线段树和树状数组都可以很优秀的通过,但我们用这道题来练一下分块

    分析:

      我们先将数列A分成若干个不超过 ⌊ √n ⌋ (下取整)的段,其中第i段的左端点为(i - 1)⌊ √n ⌋+1,右端点为min(i*⌊ √n ⌋, n),例如当n = 10的时候,我们分成4块

      我们可以先定义数字sum[i],表示第 i 块的区间和,定义add[i]表示第 i 段的“增量标记”(下面细讲),初始值为0。

    1. 对于指令 1 x y k

      (1).若x, y都在第 i 块内,则选择暴力更新,把A[x], A[x+1],......A[y-1], A[y]都加上k,同时将sun[i] 更新 为 sum[i] + (y - x + 1) * k

      (2). 否则,设x在第p块, y在第q块

        ①:对于i∈[p+1, q-1], i是一个整块,所以直接add[i] += k,表示第 i 块都加上了k。(优于暴力更新)

        ②:对于p、q所在不足一整块的,类似于(1)进行暴力更新

    2.对于指令 2 x y

      (1).若x, y都在第i块内,则(A[x] + A[x+1] + ......  + A[y-1] + A[y]) + add[i] * (y - x + 1)就是答案

      (2).否则设x在第p块, y在第q块, 令Ans = 0;

        ①:对于i∈[p+1, q-1], i是一个整块,Ans += sum[i] + add[i] * len[i];// len[i] 是第i块的长度

        ②:对于p、q所在不足一整块的,类似于(1)进行计算

    对于t, 我们令 t = ⌊ √n ⌋, len = n / t,由于n不一定是完全平方数,所以就要新开一块的操作(Code中会说明)。由于块长块数都近似于O(√n),所以时间复杂度为O((n + m) * √n)

    Code:

     1 #include <bits/stdc++.h>
     2 using namespace std;
     3 
     4 typedef long long LL;
     5 const int maxn = 1e5 + 10;
     6 
     7 // 分块求解 O((n+m) * √n) 
     8 
     9 LL Read();
    10 
    11 LL n, m, t;
    12 LL val[maxn], sum[maxn], add[maxn];
    13 int L[maxn], R[maxn], pos[maxn];
    14 
    15 inline void change(int l, int r, LL x) {
    16     int p = pos[l], q = pos[r]; // l r所在那一块
    17     if(p == q) { // 在同一块 
    18         for(int i=l; i<=r; i++) val[i] += x; // 直接更新 
    19         sum[p] += x * (r - l + 1); // 更新本块的和 
    20     }
    21     else {  // 不在同一块 
    22         for(int i=p+1; i<=q-1; ++i) add[i] += x; // p ~ q之间的整块 
    23         for(int i=l; i<=R[p]; ++i) val[i] += x; // 更新 p所在的非整块 
    24         sum[p] += x * (R[p] - l + 1); // 更新sum 
    25         for(int i=L[q]; i<=r; ++i) val[i] += x; //更新 q所在的非整块 
    26         sum[q] += x * (r - L[q] + 1); // 更新sum 
    27     }
    28 }
    29 
    30 inline LL ask(int l, int r) {
    31     int p = pos[l], q = pos[r];
    32     LL Ans = 0;
    33     if(p == q) { // 在同一块 
    34         for(int i=l; i<=r; ++i) Ans += val[i]; // 暴力求和 
    35         Ans += add[p] * (r - l + 1); // 加上此块总共加的 
    36     }
    37     else {
    38         for(int i=p+1; i<=q-1; ++i) // 加上pq所在块之间的整块 
    39             Ans += sum[i] + add[i] * (R[i] - L[i] + 1); // 加上sum和此块总共加的
    40         for(int i=l; i<=R[p]; ++i) Ans += val[i]; //加上p所在块的一些元素
    41         Ans += add[p] * (R[p] - l + 1);
    42         for(int i=L[q]; i<=r; ++i) Ans += val[i];//加上p所在块的一些元素
    43 
    44         Ans += add[q] * (r - L[q] + 1); 
    45     }
    46     return Ans;
    47 }
    48 
    49 int main() {
    50     n = Read(), m = Read();
    51     for(int i=1; i<=n; ++i) val[i] = Read();
    52     
    53     // 分块 
    54     t = sqrt(n);
    55     for(int i=1; i<=t; ++i) {
    56         L[i] = (i-1) * t + 1; // 第i块的左断电 
    57         R[i] = i * t;  // 第i块的右端点 
    58     }
    59     if(R[t] < n) { // 没有分完, 
    60         t++; // 新加一组 
    61         L[t] = R[t-1]+1; // 新的一组左端点为上一组右端点的下一个 
    62         R[t] = n; // 右端点为n 
    63     }
    64     
    65     //预处理 
    66     for(int i=1; i<=t; ++i) { // 枚举每一块 开始预处理 
    67         for(int j=L[i]; j<=R[i]; ++j) {
    68             pos[j] = i; // 记录每一个元素 
    69             sum[i] += val[j]; // 计算每一块的和 
    70         }
    71     }
    72     
    73     // solve
    74     while(m --) {
    75         int f, x, y; scanf("%d%d%d", &f, &x, &y);
    76         if(f == 1) { // 
    77             LL z = Read();
    78             change(x, y, z); 
    79         }
    80         else {
    81             //printf("%lld %lld
    ", x, y);
    82             LL Ans = ask(x, y);
    83             printf("%lld
    ", Ans);
    84         } 
    85     } 
    86     return 0;
    87 }
    88 // 快读
    89 inline LL Read() {
    90     LL x = 0, f = 1; char ch = getchar();
    91     while(!isdigit(ch)) {if(ch == '-') f = -1; ch = getchar(); }
    92     while(isdigit(ch)) { x = x * 10 + (ch-48), ch = getchar(); }
    93     return x*f;
    94 }
    View Code

    三:例题2

    Luogu P3396 哈希冲突

    众所周知,模数的hash会产生冲突。例如,如果模的数p=7,那么411便冲突了。

    B君对hash冲突很感兴趣。他会给出一个正整数序列value[]

    自然,B君会把这些数据存进hash池。第value[k]会被存进(k%p)这个池。这样就能造成很多冲突。

    B君会给定许多个px,询问在模p时,x这个池内数的总和

    另外,B君会随时更改value[k]。每次更改立即生效。

    保证1<=p<n1<=p<n.

    样例输入:

    10 5

    1 2 3 4 5 6 7 8 9 10

    A 2 1
    C 1 20
    A 3 1
    C 5 1
    A 5 0

    样例输出:

    25

    41

    11

    样例解释:

    A 2 1的答案是:1 + 3 + 5 + 7 + 9 = 25

    C 1 20后:20 2 3 4 5 6 7 8 9 10

    A 3 1的答案是:20 + 4 + 7 + 10 = 41

    C 5 1后:20 2 3 4 1 6 7 8 9 10

    A 5 0的答案是:1 + 10 = 11

    分析:

    我们可以枚举从1到⌊ √n ⌋的模数,进行 n * ⌊ √n ⌋的暴力预处理,对于p∈[1,  ⌊ √n ⌋]的询问我们可以进行O(1)的回答

    具体看代码注释:

    Code:

     1 #include <bits/stdc++.h>
     2 using namespace std;
     3 
     4 int Read();
     5 
     6 int n, m, val[150010];
     7 int t, sum[400][150010];
     8 // sum(i, j) 当模数为 i 时,余数为 j 的和 
     9 // sqrt(n) ≈388 
    10 
    11 // 更新 val[pos] 的值 
    12 inline void change(int pos, int x) {
    13     // 枚举模数,∵每一个值更改后, 对不同的模数都有影响 
    14     // sum(i, pos%i) 减去原来的数字,加上更改的数字 
    15     for(int i=1; i<=t; ++i) sum[i][pos%i] = sum[i][pos%i] - val[pos] + x;
    16     val[pos] = x; // 更新 pos 上的数字 
    17 }
    18 
    19 // 对于模数 p  求出余数为 x 的元素和 
    20 inline long long ask(int p, int x) {
    21     long long s = 0;
    22     if(p <= t) s = sum[p][x]; // ∵我们已经枚举了1~t的模数 ∴可以直接输出 
    23     else { // 模数 p 大于t 
    24         for(int i=x; i<=n; i+=p) 
    25             s += val[i];
    26     }
    27     return s;
    28 }
    29 
    30 int main() {
    31     n = Read(), m = Read();
    32     for(int i=1; i<=n; ++i) val[i] = Read();
    33     // 预处理 
    34     t = sqrt(n);
    35     for(int j=1; j<=n; ++j) // 枚举位置 
    36         for(int i=1; i<=t; ++i) // 枚举模数 p 
    37             sum[i][j%i] += val[j]; 
    38     // 询问 
    39     while(m --) {
    40         char ch[5]; scanf("%s", ch);
    41         int x = Read(), y = Read();
    42         if(ch[0] == 'A') {
    43             long long Ans = ask(x, y);
    44             printf("%lld
    ", Ans);
    45         }
    46         else change(x, y);
    47     }
    48     return 0;
    49 }
    50 
    51 inline int Read() {
    52     int x=0, f=1; char ch = getchar();
    53     while(!isdigit(ch)) {if(ch == '-') f = -1; ch = getchar(); }
    54     while(isdigit(ch)) { x = x * 10 + (ch-48), ch = getchar(); }
    55     return x*f;
    56 }
    View Code
  • 相关阅读:
    hihoCoder #1077 RMQ问题再临-线段树
    ms sql 获取字符串首字母
    如何设置gen_server在退出时执行相关操作
    C++拾遗
    [置顶] Linux下文件和目录权限说明
    Android百度地图之显示地图
    USACO March. 2012
    JNI之HelloWorld
    复习C语言系列二:动态调用函数指针数组
    HDU2527:Safe Or Unsafe(哈弗曼树)
  • 原文地址:https://www.cnblogs.com/Marginalin/p/9777061.html
Copyright © 2020-2023  润新知