数据结构 — 分块 (学习历程) 连载中......

 

  1.  区间加法  单点查值 
  • 把每m个元素分为一块,共有n/m块,每次区间加的操作会涉及O(n/m)个整块,以及区间两侧两个不完整的块中至多2m个元素。
  • 我们给每个块设置一个加法标记 atage(记录这个块中元素一起加了多少),每次操作对每个整块直接O(1)标记,而不完整的块由于元素比较少,暴力修改元素的值。每次询问时返回元素的值加上其所在块的加法标记。
  • 这样每次操作的复杂度是O(n/m)+O(m),根据均值不等式,当m取√n时总复杂度最低。
 1 #include<cstdio>
 2 #include<cmath>
 3 #include<algorithm> 
 4 using namespace std;
 5 int bl[50010];    //bl[i]代表第 i个数位于哪个块
 6 int atag[50010];    //存储每个块内 操作加值的和
 7 int block,n,a[50010];
 8 int read(){
 9     int x=0,f=1;
10     char c=getchar();
11     while(c<'0'||c>'9') {if(c=='-') f=-1;c=getchar();}    //注意大括号不能省 
12     while(c>='0'&&c<='9') x=x*10+c-'0',c=getchar();
13     return x*f;
14 }
15 void add(int l,int r,int c){
16     for(int i=l;i<=min(bl[l]*block,r);i++) a[i]+=c;    //区间覆盖的左半块
17     if(bl[l]!=bl[r]){        //不在同一个块时 
18         for(int i=(bl[r]-1)*block+1;i<=r;i++) a[i]+=c;    //区间覆盖的右半块 
19     }
20     for(int i=bl[l]+1;i<=bl[r]-1;i++) atag[i]+=c;    //中间的整块整块处理 
21 }
22 int main()
23 {
24     n=read();
25     block=sqrt(n);        //每个块的大小
26     for(int i=1;i<=n;i++) a[i]=read();
27     for(int i=1;i<=n;i++) bl[i]=(i-1)/block+1;    //分块
28     //比如 block=2 那么1 2是属于第一个块的,3 4属于第二个块 这样处理刚好能满足需求
29     for(int i=1;i<=n;i++){
30         int opt=read(),l=read(),r=read(),c=read();
31         if(opt==0) add(l,r,c);
32         else printf("%d
",a[r]+atag[bl[r]]);
33     }
34     return 0;
35 }
View Code

  2. 区间加法  询问区间内小于x的元素个数  (vector  块内排序  lower_bound)

 1 #include<cstdio>
 2 #include<vector>
 3 #include<cmath>
 4 #include<algorithm>
 5 #define N 50010
 6 using namespace std;
 7 int a[N],bl[N],atage[N],n,block;
 8 vector <int > v[N];    //动态数组 
 9 int read(){
10     int x=0,f=1;
11     char c=getchar();
12     while(c<'0'||c>'9') {if(c=='-') f=-1;c=getchar();}
13     while(c>='0'&&c<='9')  x=x*10+c-'0',c=getchar();
14     return x*f;
15 }
16 void  reset(int x){
17     v[x].clear();    //清除x块内元素 
18     for(int i=(x-1)*block+1;i<=x*block;i++) v[x].push_back(a[i]);
19     //(x-1)*block+1 表示第 x-1块内的末元素+1,也就是第 x块的第一个元素
20     // x*block 表示 第 x块的末元素 
21     sort(v[x].begin(),v[x].end());
22 }
23 void add(int l,int r,int c){
24     for(int i=l;i<=min(bl[l]*block,r);i++) a[i]+=c;    //最左边的(半)块 
25     reset(bl[l]);    //a[i]的值改变  容器里的值也要更新 
26     if(bl[l]!=bl[r]){
27         for(int i=(bl[r]-1)*block+1;i<=r;i++) a[i]+=c;    //最右边的(半)块
28         reset(bl[r]);
29     }
30     for(int i=bl[l]+1;i<=bl[r]-1;i++) atage[i]+=c;    //中间的整块整块处理 
31 }
32 void ask(int l,int r,int c){
33     int sum=0;
34     for(int i=l;i<=min(bl[l]*block,r);i++) if(a[i]+atage[bl[i]]<c) sum++;
35     if(bl[l]!=bl[r]){
36         for(int i=(bl[r]-1)*block+1;i<=r;i++) if(a[i]+atage[bl[i]]<c) sum++;
37     }
38     for(int i=bl[l]+1;i<=bl[r]-1;i++){
39         int x=lower_bound(v[i].begin(),v[i].end(),c-atage[i])-v[i].begin(); 
40         sum+=x;
41     }
42     printf("%d
",sum);
43 }
44 int main()
45 {
46     n=read();
47     block=sqrt(n);
48     for(int i=1;i<=n;i++) a[i]=read();
49     for(int i=1;i<=n;i++){
50         bl[i]=(i-1)/block+1;
51         v[bl[i]].push_back(a[i]);
52     }
53     for(int i=1;i<=bl[n];i++) sort(v[i].begin(),v[i].end());     //块内排序 
54     for(int i=1;i<=n;i++){
55         int opt=read(),l=read(),r=read(),c=read();
56         if(opt==0) add(l,r,c);
57         else ask(l,r,c*c);
58     }
59     return 0;
60 }
View Code

  3.  区间加法  询问区间内小于某个值x的前驱(比其小的最大元素   (set  lower_bound)

 1 #include<cstdio>
 2 #include<algorithm>
 3 #include<set>
 4 #define N 100010
 5 using namespace std;
 6 int read(){
 7     int x=0,f=1; char c=getchar();
 8     while(c<'0'||c>'9') {if(c=='-') f=-1; c=getchar();}
 9     while(c>='0'&&c<='9') x=x*10+c-'0',c=getchar();
10     return x*f;
11 }
12 int n,blo;
13 int v[N],bl[N],atag[N];
14 set <int > st[110];
15 void add(int l,int r,int c){
16     for(int i=l;i<=min(r,bl[l]*blo);i++){
17         st[bl[l]].erase(v[i]);
18         v[i]+=c;
19         st[bl[l]].insert(v[i]);
20     }
21     if(bl[l]!=bl[r]){
22         for(int i=(bl[r]-1)*blo+1;i<=r;i++){
23             st[bl[r]].erase(v[i]);
24             v[i]+=c;
25             st[bl[r]].insert(v[i]);
26         }
27     }
28     for(int i=bl[l]+1;i<=bl[r]-1;i++)
29         atag[i]+=c;
30 }
31 int ask(int l,int r,int c){
32     int ans=-1;
33     for(int i=l;i<=min(r,bl[l]*blo);i++){
34         int val=v[i]+atag[bl[l]];
35         if(val<c) ans=max(val,ans);
36     }
37     if(bl[l]!=bl[r]){
38         for(int i=(bl[r]-1)*blo+1;i<=r;i++){
39             int val=v[i]+atag[bl[r]];
40             if(val<c) ans=max(val,ans);
41         }
42     }
43     for(int i=bl[l]+1;i<=bl[r]-1;i++){
44         int x=c-atag[i];
45         set <int > ::iterator it=st[i].lower_bound(x);
46         if(it==st[i].begin()) continue;
47         --it;
48         ans=max(ans,*it+atag[i]);
49     }
50     return ans;
51 }
52 int main()
53 {
54     n=read(),blo=1000;
55     for(int i=1;i<=n;i++) v[i]=read();
56     for(int i=1;i<=n;i++){
57         bl[i]=(i-1)/blo+1;
58         st[bl[i]].insert(v[i]);
59     }
60     for(int i=1;i<=n;i++){
61         int opt=read(),a=read(),b=read(),c=read();
62         if(opt==0) add(a,b,c);
63         else printf("%d
",ask(a,b,c));
64     } 
65     return 0;
66 }
View Code

  4.  区间加法  区间求和 

 1 #include<cstdio>
 2 #include<cmath>
 3 #include<algorithm>
 4 #define N 50010
 5 #define ll long long        //真的不开 long long 见祖宗 T^T 
 6 using namespace std;
 7 ll read(){
 8     ll x=0,f=1; char c=getchar();
 9     while(c<'0'||c>'9') {if(c=='-') f=-1; c=getchar();}
10     while(c>='0'&&c<='9') x=x*10+c-'0',c=getchar();
11     return x*f;
12 }
13 int n,blo,bl[N];
14 ll a[N],sum[N],atag[N];        // 不能开小 
15 void add(int l,int r,int c){
16     for(int i=l;i<=min(bl[l]*blo,r);i++)
17         a[i]+=c,sum[bl[l]]+=c;
18     if(bl[l]!=bl[r]){
19         for(int i=(bl[r]-1)*blo+1;i<=r;i++)
20             a[i]+=c,sum[bl[r]]+=c;
21     }
22     for(int i=bl[l]+1;i<=bl[r]-1;i++)
23         atag[i]+=c;
24 }
25 ll ask(int l,int r){
26     ll ans=0;
27     for(int i=l;i<=min(bl[l]*blo,r);i++)
28         ans+=a[i]+atag[bl[l]];
29     if(bl[l]!=bl[r]){
30         for(int i=(bl[r]-1)*blo+1;i<=r;i++)
31             ans+=a[i]+atag[bl[r]];
32     }
33     for(int i=bl[l]+1;i<=bl[r]-1;i++)
34         ans+=sum[i]+atag[i]*blo;
35     return ans;
36 }
37 int main(){
38     n=read(),blo=sqrt(n);
39     for(int i=1;i<=n;i++) a[i]=read();
40     for(int i=1;i<=n;i++){
41         bl[i]=(i-1)/blo+1;
42         sum[bl[i]]+=a[i]; 
43     }
44     for(int i=1;i<=n;i++){
45         int opt=read(),l=read(),r=read(),c=read();
46         if(opt==0) add(l,r,c);
47         else printf("%d
",ask(l,r)%(c+1));
48     }
49     return 0;
50 }
View Code

  5.  区间求和  区间开方 

 1 #include<cstdio>
 2 #include<cmath>
 3 #include<algorithm>
 4 #define N 50010
 5 #define ll long long
 6 using namespace std;
 7 ll read(){
 8     ll x=0,f=1; char c=getchar();
 9     while(c<'0'||c>'9') {if(c=='-') f=-1; c=getchar();}
10     while(c>='0'&&c<='9') x=x*10+c-'0',c=getchar();
11     return x*f;
12 }
13 int n,blo;
14 int  bl[N],a[N],sum[N],flag[N];
15 void solve_sqrt(int x){
16     if(flag[x]) return ;
17     flag[x]=1;
18     sum[x]=0;
19     for(int i=(x-1)*blo+1;i<=x*blo;i++){
20         a[i]=sqrt(a[i]),sum[x]+=a[i];
21         if(a[i]>1) flag[x]=0;
22     }
23 }
24 void add(int l,int r,int c){
25     for(int i=l;i<=min(r,bl[l]*blo);i++){
26         sum[bl[l]]-=a[i];
27         a[i]=sqrt(a[i]);
28         sum[bl[l]]+=a[i];
29     }
30     if(bl[l]!=bl[r]){
31         for(int i=(bl[r]-1)*blo+1;i<=r;i++){
32         sum[bl[r]]-=a[i];
33         a[i]=sqrt(a[i]);
34         sum[bl[r]]+=a[i];
35         }
36     }
37     for(int i=bl[l]+1;i<=bl[r]-1;i++)
38         solve_sqrt(i);
39 }
40 int ask(int l,int r){
41     int ans=0;
42     for(int i=l;i<=min(bl[l]*blo,r);i++)
43         ans+=a[i];
44     if(bl[l]!=bl[r]){
45         for(int i=(bl[r]-1)*blo+1;i<=r;i++)
46             ans+=a[i];
47     }
48     for(int i=bl[l]+1;i<=bl[r]-1;i++)
49         ans+=sum[i];
50     return ans;
51 }
52 int main()
53 {
54     n=read(),blo=sqrt(n);
55     for(int i=1;i<=n;i++){
56         a[i]=read();
57         bl[i]=(i-1)/blo+1;
58         sum[bl[i]]+=a[i];
59     }
60     for(int i=1;i<=n;i++){
61         int opt=read(),l=read(),r=read(),c=read();
62         if(opt==0) add(l,r,c);
63         else printf("%d
",ask(l,r));
64     }
65     return 0;
66 }
View Code

  6.  单点插入,单点询问

 1 #include<cstdio>
 2 #include<cmath>
 3 #define re register
 4 #define in inline
 5 #define ll long long
 6 #define N 1010
 7 using namespace std;
 8 in int read(){
 9     int x=0,f=1; char c=getchar();
10     while(c<'0'||c>'9'){if(c=='-') f=-1; c=getchar();}
11     while(c>='0'&&c<='9') x=x*10+c-'0',c=getchar();
12     return x*f;
13 }
14 
15 int n,m,blo;
16 int new_point; //新增点数计数器 
17 int a[N][N]; //a[i][j]记录第i块的第j个数
18 int b[N][N];
19 int len[N]; //len[i]记录第i块的个数 
20 
21 void rebuild(){
22     int x=0,t=0;
23     int k=m/blo;    //m 新总长
24     if(m%blo!=0) k++;
25     m+=blo; new_point=0;
26     blo=sqrt(m);
27     for(int i=1;i<=k;i++)
28         for(int j=1;j<=len[i];j++){
29             x++;
30             int bl=(x-1)/blo+1;
31             b[bl][++t]=a[i][j];
32             if(t>=blo) t=0;
33         }
34     k=m/blo;
35     if(!m%blo) k++;
36     for(int i=1;i<k;i++){
37         len[i]=blo;
38         for(int j=1;j<=len[i];j++) a[i][j]=b[i][j];
39     }
40     if(!m%blo) len[k]=blo;
41     else len[k]=m-blo*blo;
42     for(int j=1;j<=len[k];j++) a[k][j]=b[k][j];    
43 }
44 
45 int main()
46 {
47     n=read(),m=n;
48     blo=sqrt(n);
49     for(re int i=1;i<=n;i++){
50         int x=read(),bl=(i-1)/blo+1;
51         a[bl][++len[bl]]=x;
52     }
53     for(re int i=1;i<=n;i++){
54         int opt=read(),l=read(),r=read(),c=read();
55         if(!opt){
56             new_point++;
57             
58             int x=0,t;
59             for(t=1;t<=blo;t++){    //暴力找l的位置 
60                 if(x+len[t]>=l) break;
61                 x+=len[t];
62             }
63             
64             len[t]++,l-=x;    //暴力插点 
65             for(int i=len[t];i>l;i--) a[t][i]=a[t][i-1];
66             a[t][l]=r;
67             
68             if(new_point>=blo) rebuild();    //重新分块
69         }
70         else {
71             re int x=0,t;
72             for(t=1;t<=blo;t++){
73                 if(x+len[t]>=r) break;
74                 else x+=len[t];
75             }
76             printf("%d
",a[t][r-x]);
77         }
78     }
79     return 0;
80 }
View Code

  7. 区间乘法  区间加法  单点询问 

  • 乘法优先级高于加法的实现
 1 #include<cstdio>
 2 #include<cmath>
 3 #include<algorithm>
 4 #define N 100010
 5 #define mod 10007
 6 #define ll long long
 7 using namespace std;
 8 ll read(){
 9     ll x=0,f=1; char c=getchar();
10     while(c<'0'||c>'9') {if(c=='-') f=-1; c=getchar();}
11     while(c>='0'&&c<='9') x=x*10+c-'0',c=getchar();
12     return x*f;
13 }
14 int n,blo;
15 int a[N],bl[N],atag[N],mtag[N];
16 void reset(int x){
17     for(int i=(x-1)*blo+1;i<=min(n,x*blo);i++)    //注意与n取min 
18         a[i]=(a[i]*mtag[x]+atag[x])%mod;
19         atag[x]=0,mtag[x]=1;
20 }
21 void solve(int opt,int l,int r,int c){
22     reset(bl[l]);        //乘法优先级 
23     for(int i=l;i<=min(r,bl[l]*blo);i++){
24         if(opt==0) a[i]+=c;
25         else a[i]*=c;
26         a[i]%=mod;
27     }
28     if(bl[l]!=bl[r]){
29         reset(bl[r]);        //乘法优先级
30         for(int i=(bl[r]-1)*blo+1;i<=r;i++){
31             if(opt==0) a[i]+=c;
32             else a[i]*=c;
33             a[i]%=mod;
34         }
35     }
36     for(int i=bl[l]+1;i<=bl[r]-1;i++){
37         if(opt==0) atag[i]=(atag[i]+c)%mod;
38         else{
39             atag[i]=(atag[i]*c)%mod;    // 注意 
40             mtag[i]=(mtag[i]*c)%mod;
41         }
42     }
43 }
44 int main()
45 {
46     n=read(),blo=sqrt(n);
47     for(int i=1;i<=n;i++){
48         a[i]=read();
49         bl[i]=(i-1)/blo+1;
50     }
51     for(int i=1;i<=bl[n];i++) mtag[i]=1;    //初始化mtag 
52     for(int i=1;i<=n;i++){
53         int opt=read(),l=read(),r=read(),c=read();
54         if(opt==2) printf("%d
",(a[r]*mtag[bl[r]]+atag[bl[r]])%mod);
55         else solve(opt,l,r,c);
56     }     
57     return 0;
58 }
View Code

   8. 区间询问等于一个数c的元素,并将这个区间的所有元素改为c 

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<cmath>
 4 #include<algorithm>
 5 #define N 100010
 6 #define ll long long
 7 using namespace std;
 8 ll read(){
 9     ll x=0,f=1; char c=getchar();
10     while(c<'0'||c>'9') {if(c=='-') f=-1; c=getchar();}
11     while(c>='0'&&c<='9') x=x*10+c-'0',c=getchar();
12     return x*f;
13 }
14 int n,blo;
15 int a[N],bl[N],tag[N];
16 void reset(int x){
17     if(tag[x]==-1) return ;
18     for(int i=(x-1)*blo+1;i<=x*blo;i++)
19         a[i]=tag[x];
20     tag[x]=-1;
21 }
22 int solve(int l,int r,int c){
23     int ans=0;
24     reset(bl[l]);
25     for(int i=l;i<=min(r,bl[l]*blo);i++){
26         if(a[i]!=c) a[i]=c;
27         else ans++;
28     }
29     if(bl[l]!=bl[r]){
30         reset(bl[r]);
31         for(int i=(bl[r]-1)*blo+1;i<=r;i++)
32             if(a[i]!=c) a[i]=c;
33             else ans++;
34     }
35     for(int i=bl[l]+1;i<=bl[r]-1;i++){
36         if(tag[i]!=-1){
37             if(tag[i]==c) ans+=blo;
38             else tag[i]=c;
39         }
40         else{
41             for(int j=(i-1)*blo+1;j<=i*blo;j++)
42                 if(a[j]!=c) a[j]=c;
43                 else ans++;
44             tag[i]=c;
45         }
46     }
47     return ans;
48 }
49 int main()
50 {
51     memset(tag,-1,sizeof(tag));
52     n=read(),blo=sqrt(n);
53     for(int i=1;i<=n;i++){
54         a[i]=read();
55         bl[i]=(i-1)/blo+1;
56     }
57     for(int i=1;i<=n;i++){
58         int l=read(),r=read(),c=read();
59         printf("%d
",solve(l,r,c));
60     }
61     return 0;
62 }
View Code

   9.  区间的最小众数 [ 未AC 88分 ]

 1 #include <cstdio>
 2 #include <cstring>
 3 #include <algorithm>
 4 #include <cmath>
 5 #include <map>
 6 #include <vector>
 7 using namespace std;
 8 int pos[110000], v[110000], cnt[110000], a[110000];
 9 int id, n, N;
10 int f[510][510];       //块i到j之间的最小众数的id
11 map<int, int> m;       //记录每个数的id
12 vector<int> b[51000];  //记录每个数出现的第一个位置
13 void dp(int x) {
14     memset(cnt, 0, sizeof(cnt));
15     int maxx = 0, ss = 0;  //最大的id及最大的值
16     for (int i = (x - 1) * N + 1; i <= n; i++) {
17         int cc = ++cnt[a[i]];  //统计每个数出现的个数
18         if (cc > ss || cc == ss && v[a[i]] < v[maxx]) {
19             ss = cc;
20             maxx = a[i];
21         }
22         f[x][pos[i]] = maxx;
23     }
24 }
25 int along(int l, int r, int x)  // ask how long
26 {
27     int ans = upper_bound(b[x].begin(), b[x].end(), r) -
28               lower_bound(b[x].begin(), b[x].end(), l);  //求l到r在x块的长度(r的后继-l的前驱)
29     return ans;
30 }
31 int solve(int x, int y) {
32     int ss = 0, maxx = 0;
33     //完整的块
34     maxx = f[pos[x] + 1][pos[y] - 1];
35     ss = along(x, y, maxx);
36     //不完整的块
37     for (int i = x; i <= min(pos[x] * N, y); i++) {
38         int cc = along(x, y, a[i]);
39         if (cc > ss || cc == ss && v[a[i]] < v[maxx]) {
40             ss = cc;
41             maxx = a[i];
42         }
43     }
44     if (pos[x] != pos[y]) {
45         for (int i = (pos[y] - 1) * N + 1; i <= y; i++) {
46             int cc = along(x, y, a[i]);
47             if (cc > ss || cc == ss && v[a[i]] < v[maxx]) {
48                 ss = cc;
49                 maxx = a[i];
50             }
51         }
52     }
53     return maxx;
54 }
55 int main() {
56     scanf("%d", &n);
57     N = sqrt(n);
58     for (int i = 1; i <= n; i++) {
59         scanf("%d", &a[i]);
60         pos[i] = (i - 1) / N + 1;
61         if (m[a[i]] == 0) {
62             m[a[i]] = ++id;  // a[i]是第几个出现的
63             v[id] = a[i];
64         }
65         a[i] = m[a[i]];        //强制重新编号
66         b[a[i]].push_back(i);  //记录每一个出现的位置
67     }
68     for (int i = 1; i <= pos[n]; i++) dp(i);
69     for (int i = 1; i <= n; i++) {
70         int x, y;
71         scanf("%d%d", &x, &y);
72         if (x > y)
73             swap(x, y);
74         printf("%d
", v[solve(x, y)]);
75     }
76     return 0;
77 }
View Code
原文地址:https://www.cnblogs.com/RR-Jin/p/11623274.html