浅谈分块——入门

前言

  在学会分块之前,觉得分块是一个很深奥的东西,很玄学。但其实分块的作用也很广泛,也非常简单,在这里分享一下。

分块的定义与分块的基本性质

  分块,顾名思义,就是将一个数组分成一些小块。

而分块有一个基本性质,就是块的大小不会影响答案,只对时间有一定影响。

一般有以下三种分块方式:

No.1: 固定长度分块。

No.2 : 长度为sqrt(n)

No.3 : 每块随机长度。

常用的话一般选择No.1或No.2。

分块

如果选择No.2的分块方式的话,我们定义以下几个变量:

1 block = (int)sqrt(n) //块的长度
2
3 sum = n / block;
4 if (n % block) sum++;//块数
5
6 p[i] = (i - 1) / block + 1;//第i个元素在第几块中

  这几个变量十分有用,在后文中会讲到。

以上是分块的初始化。

下面我们来讲对于查询怎么对付,

对于区间查询,有三种情况:

1:询问区间[x,y]中x不是块的左边界,y也不是块的右边界。

2:询问区间[x,y]中x不是块的左边界,y也是块的右边界。

3:询问区间[x,y]中x是块的左边界,y也不是块的右边界。

我们发现,对于比较普通的1情况可以把询问区间分成3个部分:

左边多出来的、中间的k块、右边多出来的。

可以发现,对于一块来说查询是O(1)的(因为我可以预处理每一块的值)

对于多出来的边角料部分总和不足2*sqrt(n),基于这个时间复杂度,我们可以进行暴力求解。

所以可以发现对于1操作查询的总复杂度(一共有n次操作)为O(n*sqrt(n))。

对于2、3情况,我们可以将这些情况当成1情况来处理。

代码展示:

 1 for (int i = x; i <= min(y,p[x] * block); i++)//处理左边角料
 2 //do something
 3
 4  for (int i = (p[y] - 1) * block + 1; i <= y; i++)//处理右边角料
 5  //do something
 6
 7 for(int i = p[x] + 1; i <= p[y] - 1; i++)//处理中间k块
 8  //do something
 9
10 //这里特别注意一下,对于一个块的左边界,我们可以求出上一个块的右边界(p[x] - 1)*block,+1后就是这个块的左边界。

现在我们来讲一下修改操作。

跟查询操作一样,我们也可以分成3部分,对于边角料暴力修改a[i],对于一块,我们修改这一块的值A[p[i]]

总时间复杂度为O(n*sqrt(n))。

代码展示:

for (int i = x; i <= min(y,p[x] * block); i++)//处理左边角料
  change(a[i]);

for (int i = (p[y] - 1) * block + 1; i <= y; i++)//处理右边角料
  change(a[i])

for(int i = p[x] + 1; i <= p[y] - 1; i++)//处理中间k块
  change(A[i]);//这里的i已经是块号了,所以修改的是A[i],与讲解的不太一样

对于预处理,我们发现即使每个块暴力进行对于A[i]的预处理,时间复杂度也只有O(n),

所以预处理就块内暴力求解。

代码展示:

void work(int x,int y,int t)
{
    int st = 0;
    for (int i = x; i <= y; i++)
         //do something with st
     A[t] = st;
}

for (int i = 1; i <= sum; i++)//一共sum块
  work((p[i] - 1) * block + 1,p[i] * block,i);//传入块的左右边界和编号

几种常用的(入门)模板

1:区间加法,单点求和。(loj6277)

预处理时求得每个块内的和,修改时对于边角料暴力修改值。

对于中间的k块,修改加法标记addtag[i],表示第i个块加上了addtag[i]。

对于询问,我们要记下对于一个块一共加上了多少(记在addtag里),最后输出a[i] + addtag[p[i]]

完整代码展示:

 1 #include<bits/stdc++.h>
 2 #define int long long
 3 using namespace std;
 4 int block,sum,Ans[50005],p[50005],n,m,Add[50005];
 5 int a[50005];
 6 inline void work(int as,int bs,int x)
 7 {
 8     int st = 0;
 9     for (int i = as; i <= bs; i++)
10       st += a[i];
11     Ans[x] = st;
12 }
13 inline int read(){
14    int s=0,w=1;
15    char ch=getchar();
16    while(ch<‘0‘||ch>‘9‘){if(ch==‘-‘)w=-1;ch=getchar();}
17    while(ch>=‘0‘&&ch<=‘9‘) s=s*10+ch-‘0‘,ch=getchar();
18    return s*w;
19 }
20 signed main(){
21     scanf("%lld",&n);
22     block = (int)sqrt(n);
23     sum = n / block;
24     if (n % block) sum++;
25     for (int i = 1; i <= n; i++)
26     {
27         //scanf("%lld",&a[i]);
28         a[i] = read();
29         p[i] = (i - 1) / block + 1;
30     }
31     for (int i = 1; i <= sum; i++)
32        work((i - 1) * block + 1,i * block,i);//预处理块内和
33     for (int i = 1; i <= n; i++)
34     {
35         int x,y,st = 0,op,k,mp,io;
36         op = read();
37         if (op == 1)
38         {
39           mp = read();
40           x = read();
41           io = read();
42           printf("%lld\n",a[x] + Add[p[x]]);//输出对于a[i]加上了多少
43        } else
44        {
45              x = read();
46              y = read();
47              k = read();
48              for (int j = x; j <= min(p[x] * block,y); j++)
49                a[j] += k;
50              if (p[x] != p[y])
51                for (int j = (p[y] - 1) * block + 1; j <= y; j++)
52                  a[j] += k;
53            for (int j = p[x] + 1; j <= p[y] - 1; j++)
54              Add[j] += k;//这里直接修改加法标记
55        }
56     }
57     return 0;
58 } 

2:区间加法,区间求和。(loj6280)

  预处理时求得每个块内的和,修改时对于边角料暴力修改值,修改了a[i]的值,也要把a[i]所在块的值修改。

对于中间的k块,修改加法标记addtag[i],表示第i个块加上了addtag[i]。

对于询问,我们要记下对于一个块一共加上了多少(记在addtag里),对于边角料同上一个模板一样,暴力+a[i]+addtag[p[i]]

对于中间的k块加上A[i],因为其中有block个数,所以要+block个addtag[i]

完整代码展示:

 1 #include<bits/stdc++.h>
 2 #define int long long
 3 using namespace std;
 4 const long long inf = 66666666233;
 5 int block,sum,Ans[3000005],p[3000005],n,m,Add[3000005];
 6 int a[3000005];
 7 void work(int as,int bs,int x)
 8 {
 9     int st = 0;
10     for (int i = as; i <= bs; i++)
11       st += a[i];
12     Ans[x] = st;
13 }
14 signed main(){
15     scanf("%lld",&n);
16     block = (int)sqrt(n);
17     sum = n / block;
18     if (n % block) sum++;
19     for (int i = 1; i <= n; i++)
20     {
21         scanf("%lld",&a[i]);
22         p[i] = (i - 1) / block + 1;
23     }
24     for (int i = 1; i <= sum; i++)
25        work((i - 1) * block + 1,i * block,i);
26     for (int i = 1; i <= n; i++)
27     {
28         int x,y,st = 0,op,k;
29         scanf("%lld",&op);
30         if (op == 1)
31         {
32           scanf("%lld%lld%lld",&x,&y,&k);
33           for (int j = x; j <= y; j)
34           if ((j - 1) % block || (p[j] * block > y))
35             {
36                 st +=  Add[p[j]] + a[j];
37                 j++;
38           } else
39           {
40               st += Ans[p[j]] + Add[p[j]] * ((p[j] * block) - ((p[j] - 1) * block + 1) + 1);//加上block个Add[p[j]]
41               j += block;
42           }
43           printf("%lld\n",st % (k + 1));
44        } else
45        {
46              scanf("%lld%lld%lld",&x,&y,&k);
47              for (int j = x; j <= y; j)
48           if ((j - 1) % block || (p[j] * block > y))
49             {
50                 a[j] += k;
51                 Ans[p[j]] += k;
52                 j++;
53           } else
54           {
55               Add[p[j]] += k;
56               j += block;
57           }
58        }
59     }
60     return 0;
61 } 

3:区间开方,区间求和。(loj6281)

  预处理时求得每个块内的和,修改时对于边角料暴力修改值,修改了a[i]的值,也要把a[i]所在块的值修改。

对于中间的k块,修改总和A[i],表示第i个块减少了。

对于询问,我们要记下对于一个块一共加上了多少(记在addtag里),边角料暴力+a[i]

对于中间的k块加上A[i],输出答案。

完整代码展示:

 1 #include<bits/stdc++.h>
 2 #define int long long
 3 using namespace std;
 4 int block,sum,Ans[50005],p[50005],n,m,Add[50005],tag[500005];
 5 int a[50005];
 6 inline void work(int as,int bs,int x)
 7 {
 8     int st = 0;
 9     for (int i = as; i <= bs; i++)
10       st += a[i];
11     Add[x] += st;
12     //COp[x] = st;
13 }
14 inline int read(){
15    int s=0,w=1;
16    char ch=getchar();
17    while(ch<‘0‘||ch>‘9‘){if(ch==‘-‘)w=-1;ch=getchar();}
18    while(ch>=‘0‘&&ch<=‘9‘) s=s*10+ch-‘0‘,ch=getchar();
19    return s*w;
20 }
21 void push_down(int x){
22     Add[x]=0;
23     int flag=1;
24     for(int i=(x-1)*block+1;i<=min(n,x*block);i++){
25         a[i]=(int)sqrt(a[i]*1.0);
26         Add[x]+=a[i];
27         if(a[i]!=1) flag=0;
28     }
29     tag[x]=flag;
30 }//这里注意一下,对于中间的块要标记下传,重新更新
31 signed main(){
32     scanf("%lld",&n);
33     block = (int)sqrt(n);
34     sum = n / block;
35     if (n % block) sum++;
36     for (int i = 1; i <= n; i++)
37     {
38         a[i] = read();
39         p[i] = (i - 1) / block + 1;
40     }
41     for (int i = 1; i <= sum; i++)
42        work((i - 1) * block + 1,i * block,i);
43     for (int i = 1; i <= n; i++)
44     {
45         int x,y,st = 0,op,k,mp,io;
46         op = read();
47         if (op == 1)
48         {
49           x = read();
50           y = read();
51           mp = read();
52           for (int j = x; j <= min(p[x] * block,y); j++)
53                st += a[j];
54              if (p[x] != p[y])
55              for (int j = (p[y] - 1) * block + 1; j <= y; j++)
56                 st += a[j];
57           for (int j = p[x] + 1; j <= p[y] - 1; j++)
58              st += Add[j];
59           printf("%d\n",st);
60        } else
61        {
62              x = read();
63              y = read();
64              k = read();
65              for (int j = x; j <= min(p[x] * block,y); j++)
66              {
67                  int uy = a[j];
68                a[j] = (int)sqrt(a[j]);
69                Add[p[j]] -= uy - a[j];//开方就等于减少了sqrt(a[i])
70          }
71              if (p[x] != p[y])
72                for (int j = (p[y] - 1) * block + 1; j <= y; j++)
73                   {
74                       int uy = a[j];
75                     a[j] = (int)sqrt(a[j]);
76                        Add[p[j]] -= uy - a[j];
77                 }
78            for (int j = p[x] + 1; j <= p[y] - 1; j++)
79            if (tag[j] == 0) push_down(j);
80       }
81     }
82     return 0;
83 } 

4:区间加法,区间询问小于c2的数。(loj6278、luoguP4145)

  预处理时对于所有块进行快内排序,将未排序的a数组赋值给tlps,修改时对于边角料暴力修改值,修改tlps[i]的值(如果修改的是a的话,因为排过序,会发现位置不对)。

对于中间的k块,修改A[i]。

对于询问,边角料暴力判断

对于中间的k块(对于a数组)lower_bound c^2 - addtag(因为询问所有a + addtag 等价于 c^2 - addtag),得出这个位置,可以发现,这个位置的前面都是符合要求的。

完整代码展示:

 1 #include<bits/stdc++.h>
 2 #define int long long
 3 using namespace std;
 4 int block,sum,Ans[50005],p[50005],n,m,Add[50005];
 5 int a[50005],tlps[50005];
 6 inline void work(int as,int bs)
 7 {
 8     for (int i = as; i <= bs; i++)
 9          a[i] = tlps[i];
10     sort(a + as,a + bs + 1);
11 }
12 signed main(){
13     //freopen("a1.in","r",stdin);
14     //freopen("ate.out","w",stdout);
15     memset(Add,0,sizeof(Add));
16     scanf("%lld",&n);
17     block = (int)sqrt(n);
18     sum = n / block;
19     if (n % block) sum++;
20     for (int i = 1; i <= n; i++)
21     {
22         scanf("%lld",&a[i]);
23         tlps[i] = a[i];//注意要复制一份
24         p[i] = (i - 1) / block + 1;
25     }
26     for (int i = 1; i <= sum; i++)
27        work((i - 1) * block + 1,min(i * block,n));
28     for (int i = 1; i <= n; i++)
29     {
30         int x,y,op,k;
31         scanf("%lld",&op);
32         if (op == 1)
33         {
34           scanf("%lld%lld%lld",&x,&y,&k);
35              k = k * k;
36              int v = 0;
37              for (int j = x; j <= min(p[x] * block,y); j++)
38              if (tlps[j] + Add[p[x]] < k)
39                v++;
40              if (p[x] != p[y])
41                for (int j = (p[y] - 1) * block + 1; j <= y; j++)
42                  if (tlps[j] + Add[p[y]] < k)
43                    v++;
44           for (int j = p[x] + 1; j <= p[y] - 1; j++)
45             v += lower_bound(a + (j - 1) * block + 1,min(a + n,a + j * block + 1),k - Add[j]) - (a + (j - 1) * block + 1);//二分查询
46           printf("%lld\n",v);
47         } else
48         {
49              scanf("%lld%lld%lld",&x,&y,&k);
50              for (int j = x; j <= min(p[x] * block,y); j++)
51           {
52                tlps[j] += k;//修改的是原数组
53             //a[j] += k;
54           }
55              work((p[x] - 1) * block + 1,min(n,p[x] * block));
56              if (p[x] != p[y])
57              {
58                for (int j = (p[y] - 1) * block + 1; j <= y; j++)
59             {
60                  tlps[j] += k;
61               //a[j] += k;
62             }
63                work((p[y] - 1) * block + 1,min(n,p[y] * block));
64           }
65           for (int j = p[x] + 1; j <= p[y] - 1; j++)
66             Add[j] += k;
67         }
68     }
69     return 0;
70 }

原文地址:https://www.cnblogs.com/taoyc/p/10126184.html

时间: 2024-08-03 13:20:07

浅谈分块——入门的相关文章

Core Data浅谈初级入门

Core Data是iOS5之后才出现的一个框架,它提供了对象-关系映射(ORM)的功能,即能够将OC对象转化成数据,保存在SQLite数据库文件中,也能够将保存在数据库中的数据还原成OC对象.在此数据操作期间,我们不需要编写任何SQL语句,这个有点类似于著名的hibernate持久化框架,不过功能肯定是没有Hibernate强大的.简单地用下图描述下它的作用: 左边是关系模型,即数据库,数据库里面有张person表,person表里面有id.name.age三个字段,而且有2条记录: 右边是对

ios scrollview浅谈(入门)

今天小小激励了下,感谢各位以及csdn的朋友们的支持.我会给大家带来更好的文章,今天最后一篇关于scrollview来做个入门介绍,相信很多朋友已经迫不及待了,哈哈.下面我们进入主题. 新建一个工程: #import "ViewController.h" @interface ViewController () { //存scrollview UIScrollView *_sv; } @end @implementation ViewController - (void)viewDid

浅谈JavaWEB入门必备知识之Servlet入门案例详解

工欲善其事.必先利其器,想要成为JavaWEB高手那么你不知道servlet是一个什么玩意的话,那就肯定没法玩下去,那么servlet究竟是个什么玩意?下面,仅此个人观点并通过一个小小的案例来为大家详述一下什么是servlet... 个人观点:说白了,servlet就是一个java应用程序.一个运行在服务器上java类,servlet就是java处理web请求的一种机制,它具有独立于平台和协议的特性,可以生成动态的Web页面.再形象点,就是你通过IE等浏览器发送一个http请求后会根据你请求的内

浅谈Hibernate入门

前言 最近打算做一个自己的个人网站,经过仔细思考,打算使用hibernate作为开发的ORM框架,因此各种找资料,由于本人是刚刚接触这技术的,所以就找了比较基础的知识来分享下 基本概述 Hibernate是一个开放源代码的对象关系映射框架,它对JDBC进行了非常轻量级的对象封装,使得Java程序员可以随心所欲的使用对象编程思维来操纵数据库.Hibernate可以应用在任何使用JDBC的场合,既可以在Java的客户端程序使用,也可以在Servlet/JSP的Web应用中使用,最具革命意义的是,Hi

浅谈分块思想在一类数据处理问题中的应用

以形助数 正如前文所述,一些试题中繁杂的代数关系身后往往隐藏着丰富的几何背景,而借助背景图形的性质,可以使那些原本复杂的数量关系和抽象的概念,显得直观,从而找到设计算法的捷径. [例一]Raney引理的证明 [题意简述] 设整数序列A = {Ai, i=1, 2, …, N},且部分和Sk=A1+…+Ak,序列中所有的数字的和SN=1. 证明:在A的N个循环表示[1]中,有且仅有一个序列B,满足B的任意部分和Si均大于零. [分析] 先来看一个例子,若有序列A = <1, 4, -5, 3, -

浅谈SQL优化入门:1、SQL查询语句的执行顺序

1.SQL查询语句的执行顺序 (7) SELECT (8) DISTINCT <select_list> (1) FROM <left_table> (3) <join_type> JOIN <right_table> (2) ON <join_condition> (4) WHERE <where_condition> (5) GROUP BY <group_by_list> (6) HAVING <having_

浅谈WebLogic和Tomcat

浅谈WebLogic和Tomcat 分类: Java Web2011-11-30 21:19 54484人阅读 评论(19) 收藏 举报 weblogictomcat应用服务器ejbservletjava J2ee开发主要是浏览器和服务器进行交互的一种结构.逻辑都是在后台进行处理,然后再把结果传输回给浏览器.可以看出服务器在这种架构是非常重要的. 这几天接触到两种Java的web服务器,做项目用的Tomcat,看视频看的是WebLogic Server(WLS),都是web服务器,有什么区别和联

浅谈移动前端的最佳实践(转)

前言 这几天,第三轮全站优化结束,测试项目在2G首屏载入速度取得了一些优化成绩,对比下来有10s左右的差距: 这次优化工作结束后,已经是第三次大规模折腾公司框架了,这里将一些自己知道的移动端的建议提出来分享下,希望对各位有用 文中有误请您提出,以免误人自误 技术选型 单页or多页 spa(single page application)也就是我们常常说的web应用程序webapp,被认为是业内的发展趋势,主要有两个优点: ① 用户体验好 ② 可以更好的降低服务器压力 但是单页有几个致命的缺点:

浅谈Swift之己见

WWDC 2014,给了我们很多惊喜,对于开发者,Swift无疑给了我们太大的惊讶,前些天看见一篇文章道:今天在微博上不转发Swift相关的东西都不好意思说自己是程序员了,我自己的拙见,再加上苹果的敢于破旧立新,这门语言很快就会取代OC的位置,毕竟OC语法太别扭了,我是做windows路线以及J2EE的,从C#和JAVA去转向OC,天哪,简直就是噩梦,当我写J2EE烦了,我就去看看OC,顿时就找回幸福感,所以,就像有的人天生长的丽质,有的人,天生长的就是励志,好了,开玩笑,我们现在有一共同的特点