bzoj3343 教主的魔法【分块入门】By cellur925

题意:维护一个数列,给出维护区间加法,询问区间内大于等于某个值的元素个数。

 

算法:分块。因为本题第二问显然可以用二分的思想,但是这貌似并不符合区间可加性,线段树好像就不好用了呢。所以本蒟蒻学习了分块。

 

这大概是本蒟蒻的第一题正式分块,思想是在hzwer学长的分块入门学的==。

 

什么是分块?我们维护数列(貌似树上也可以)信息时可以先采用分治的思想,把数列分成若干连续的块,维护信息可以统一在块上进行维护。假如有一个n元素的数列,根据均值不等式的数学知识(并不会证明),我们把每个块的大小设为根号n可以达到最右效果。但是显然有时根号n并不是整数==,没有关系,我们的数列上大部分都在整块上,只有小部分被遗弃的元素在不完整的块中。

我们进行区间操作时,把在整块中的元素进行整体操作,那些在不完整的块中的元素进行暴力操作

总结来说,就是“大块维护,小段朴素”,这也是分块最基本的思想。

分块的复杂度(根号算法)虽然较其他数据结构可能略高了一些,却是打暴力的好方法,思维较简单,处理问题的范围更广,码量...可能略大(?)

 

回到本题的两个操作:

 

区间加法:对于一段区间,如果存在被整块覆盖的情况,直接在整块的加法标记上记录,不需在原序列上改动,查询时在加上加法标记;而在不完整的块中的元素,直接暴力修改。

查询大于等于X的元素个数:想一想如果数列是无序的,我们分的块就完全没用了。但是如果我们在预处理时以及修改时对每个整块进行排序,查询时调用lower_bound/手写二分查找,问题就能轻松得到解决。那些在不完整块中的元素,同理,暴力枚举统计。

 

综上,我们不难发现,使用分块算法需要或者只需要想的两个关键:

  怎么维护整块?怎么维护不完整的块?

 

分块的更多代码实现细节:

  (几乎每到分块题都要的)bl[i]记录i在第几个块

  bl[i]*blo(blo=sqrt(n))可以得出当前位置(块的右边界),在此基础上变形

  右边界:取min,bl[l]*blo,n(防止越界)

Code

 1 // luogu-judger-enable-o2
 2 #include<cstdio>
 3 #include<algorithm>
 4 #include<cmath>
 5 #include<vector>
 6 
 7 using namespace std;
 8 
 9 int n,Q,blo;
10 int val[1000090],bl[1000090],addtag[1000090];
11 char opt[5];
12 vector<int>cnt[5000];
13 
14 void update(int x)
15 {
16     cnt[x].clear();
17     for(int i=(x-1)*blo+1;i<=min(x*blo,n);i++)
18         cnt[x].push_back(val[i]);
19     sort(cnt[x].begin(),cnt[x].end());
20 }
21 
22 void add(int l,int r,int p)
23 {
24     if(l==r) val[l]+=p;
25     else
26     {
27         for(int i=l;i<=min(bl[l]*blo,r);i++)
28         val[i]+=p;
29         update(bl[l]);    
30         if(bl[l]!=bl[r])
31         {
32             for(int i=(bl[r]-1)*blo+1;i<=r;i++)
33                 val[i]+=p;
34             update(bl[r]);
35         }
36         for(int i=bl[l]+1;i<=bl[r]-1;i++)
37             addtag[i]+=p;
38     }
39 }
40 
41 int query(int l,int r,int p)
42 {
43     int ans=0;
44     for(int i=l;i<=min(bl[l]*blo,r);i++)
45         if(val[i]+addtag[bl[l]]>=p) ans++;
46     if(bl[l]!=bl[r])
47     {
48         for(int i=(bl[r]-1)*blo+1;i<=r;i++)
49             if(val[i]+addtag[bl[r]]>=p) ans++;
50     }
51     for(int i=bl[l]+1;i<=bl[r]-1;i++)
52     {
53         int tmp=p-addtag[i];
54         int pos=lower_bound(cnt[i].begin(),cnt[i].end(),tmp)-cnt[i].begin();
55            ans+=blo-pos;
56 //          ans+=find(i,tmp);
57     }
58     return ans;
59 }
60 
61 int main()
62 {
63     scanf("%d%d",&n,&Q);
64     for(int i=1;i<=n;i++) scanf("%d",&val[i]);
65     blo=sqrt(n);
66     for(int i=1;i<=n;i++)
67     {
68         bl[i]=(i-1)/blo+1;
69         cnt[bl[i]].push_back(val[i]);
70     } 
71     for(int i=1;i<=bl[n];i++)
72         sort(cnt[i].begin(),cnt[i].end());
73     while(Q--)
74     {
75         scanf("%s",opt+1);
76         int x=0,y=0,z=0;
77         scanf("%d%d%d",&x,&y,&z);
78         if(opt[1]=='M')
79             add(x,y,z);
80         else if(opt[1]=='A')
81             printf("%d\n",query(x,y,z));
82     }
83     return 0;
84 }
代码纯享版
 1 // luogu-judger-enable-o2
 2 #include<cstdio>
 3 #include<algorithm>
 4 #include<cmath>
 5 #include<vector>
 6 
 7 using namespace std;
 8 
 9 int n,Q,blo;
10 int val[1000090],bl[1000090],addtag[1000090];
11 char opt[5];
12 vector<int>cnt[5000];
13 
14 void update(int x)//针对散块 
15 {//清空,重新读入 
16     cnt[x].clear();
17     for(int i=(x-1)*blo+1;i<=min(x*blo,n);i++)
18         cnt[x].push_back(val[i]);
19     sort(cnt[x].begin(),cnt[x].end());
20 }
21 
22 void add(int l,int r,int p)
23 {
24     if(l==r) val[l]+=p;
25     else
26     {
27         for(int i=l;i<=min(bl[l]*blo,r);i++)
28             val[i]+=p;//左散块 
29         update(bl[l]);//重新排序    
30         if(bl[l]!=bl[r])
31         {//右散块 
32             for(int i=(bl[r]-1)*blo+1;i<=r;i++)
33                 val[i]+=p;
34             update(bl[r]);
35         }
36         //整块 
37         for(int i=bl[l]+1;i<=bl[r]-1;i++)
38             addtag[i]+=p;
39     }
40 }
41 
42 int query(int l,int r,int p)
43 {
44     int ans=0;
45     //左半散块 
46     for(int i=l;i<=min(bl[l]*blo,r);i++)
47         if(val[i]+addtag[bl[l]]>=p) ans++;
48     if(bl[l]!=bl[r])// !
49     {//右半散块 
50         for(int i=(bl[r]-1)*blo+1;i<=r;i++)
51             if(val[i]+addtag[bl[r]]>=p) ans++;
52     }
53     for(int i=bl[l]+1;i<=bl[r]-1;i++)//整块 
54     {
55         int tmp=p-addtag[i];//求>=p,则先把加法标记减去 
56         int pos=lower_bound(cnt[i].begin(),cnt[i].end(),tmp)-cnt[i].begin();
57            ans+=blo-pos;//注意这句 精髓 
58 //          ans+=find(i,tmp);
59     }
60     return ans;
61 }
62 
63 int main()
64 {
65     scanf("%d%d",&n,&Q);
66     for(int i=1;i<=n;i++) scanf("%d",&val[i]);
67     blo=sqrt(n);
68     for(int i=1;i<=n;i++)
69     {
70         bl[i]=(i-1)/blo+1;
71         cnt[bl[i]].push_back(val[i]);//记录每个块中元素 
72     } 
73     //对每个块中元素进行排序 
74     for(int i=1;i<=bl[n];i++)
75         sort(cnt[i].begin(),cnt[i].end());
76     while(Q--)
77     {
78         scanf("%s",opt+1);
79         int x=0,y=0,z=0;
80         scanf("%d%d%d",&x,&y,&z);
81         if(opt[1]=='M')
82             add(x,y,z);
83         else if(opt[1]=='A')
84             printf("%d\n",query(x,y,z));
85     }
86     return 0;
87 }
贴心注释版

然鹅不吸氧会T掉一个点,可能是我lowerbound常数的锅锅?讨论里有人说要用longlong结果我用了longlong只会带来无端的CE/WA/RE。

Just be cautious.

posted @ 2018-09-13 20:31  cellur925&Chemist  阅读(135)  评论(3编辑  收藏