分块-区间求和

一:分块

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

二:例题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\n", x, y);
82             LL Ans = ask(x, y);
83             printf("%lld\n", 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 }

四:例题2

题目描述

众所周知,模数的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.

Luogu P3396

原文地址:https://www.cnblogs.com/Marginalin/p/9777061.html

时间: 2024-10-07 21:32:54

分块-区间求和的相关文章

[用CDQ分治解决区间加&amp;区间求和]【习作】

[前言] 作为一个什么数据结构都不会只会CDQ分治和分块的蒟蒻,面对区间加&区间求和这么难的问题,怎么可能会写线段树呢 于是,用CDQ分治解决区间加&区间求和这篇习作应运而生 [Part.I]区间加&区间求和的数据结构做法 [一]线段树 裸题... 1141ms #include <iostream> #include <cstdio> #include <algorithm> #include <cstring> #include

HDU4027 Can you answer these queries 线段树区间求和+剪枝

给了你n,然后n个数字在一个数组中,接下来m个询问,每个询问三个数字 t,x,y,若t==0,那么修改区间[x,y]的每一个值,变为原来每个位置上的数 开根号取整,若t==1,那么对区间[x,y]求和 由于n,m,很大,所以树状数组铁定超时,若直接用线段树来做区间修改,那么也是超时,这类题目没别的方法了,静心剪枝,发现题目给的数据范围为2^63,有没有发现,2^63开根号 绝对不需要开10次,就能到1,到1以后就不需要再开了,意思就是若有某个区间[x,y]每一个点的值都为1时,这一段区间事实上是

【线段树(单点修改,区间求和)】HDU1166 - 敌军布阵

hdu1166 敌兵布阵,单点修改,区间求和. [ATTENTION]MAXN要开成节点数的4倍,开得不够会提示TLE. 1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #define lson l,m,root<<1 5 #define rson m+1,r,root<<1|1 6 using namespace std; 7 const int MAXN=50000*

算法模板——线段树3(区间覆盖值+区间求和)

实现功能——1:区间覆盖值:2:区间求和 相比直接的区间加,这个要注重顺序,因为操作有顺序之分.所以这里面的tag应该有个pushup操作(本程序中的ext) 1 var 2 i,j,k,l,m,n,a1,a2,a3,a4:longint; 3 a,b,d:array[0..100000] of longint; 4 function max(x,y:longint):longint;inline; 5 begin 6 if x>y then max:=x else max:=y; 7 end;

算法模板——线段树4(区间加+区间乘+区间覆盖值+区间求和)

实现功能——1:区间加法 2:区间乘法 3:区间覆盖值 4:区间求和 这是个四种常见线段树功能的集合版哦...么么哒(其实只要协调好三种tag的关系并不算太难——前提是想明白了线段树的工作模式) 代码长度几经修改后也大为缩水 还有!!!——通过BZOJ1798反复的尝试,我的出来一个重要结论——尽量减少pushup操作的不必要使用次数,对于程序提速有明显的效果!!! 1 type vet=record 2 a0,a1:longint; 3 end; 4 var 5 i,j,k,l,m,n,a1,

算法模板——线段树1(区间加法+区间求和)

实现功能——1:区间加法:2:区间求和 最基础最经典的线段树模板.由于这里面操作无顺序之分,所以不需要向下pushup,直接累积即可 1 var 2 i,j,k,l,m,n,a1,a2,a3,a4:longint; 3 a,b:array[0..100000] of longint; 4 function max(x,y:longint):longint;inline; 5 begin 6 if x>y then max:=x else max:=y; 7 end; 8 function min

vijos1740 聪明的质监员 (二分、区间求和)

http://www.rqnoj.cn/problem/657 https://www.vijos.org/p/1740 P1740聪明的质检员 请登录后递交 标签:NOIP提高组2011[显示标签] 描述 小 T 是一名质量监督员,最近负责检验一批矿产的质量.这批矿产共有n个矿石,从1到n逐一编号,每个矿石都有自己的重量wi以及价值vi.检验矿产的流程是: 1.给定m个区间[Li,Ri]: 2.选出一个参数W: 3.对于一个区间[Li,Ri],计算矿石在这个区间上的检验值Yi: Yi = ∑1

CodeForces 52C Circular RMQ(区间循环线段树,区间更新,区间求和)

转载请注明出处:http://blog.csdn.net/u012860063 题目链接:http://codeforces.com/problemset/problem/52/C You are given circular array a0,?a1,?...,?an?-?1. There are two types of operations with it: inc(lf,?rg,?v) - this operation increases each element on the segm

HDU 4027 Can you answer these queries? (线段树区间求和)

Can you answer these queries? Time Limit: 4000/2000 MS (Java/Others)    Memory Limit: 65768/65768 K (Java/Others)Total Submission(s): 12290    Accepted Submission(s): 2912 Problem Description A lot of battleships of evil are arranged in a line before