分块

                             分块

分块,顾名思义,就是将序列分成一小块一小块的来处理和维护。

我们把一段当成一个整体,只记录整体有关的信息,这就是分块。

首先,我们考虑一道题。

1.区间修改,查询区间中一段区间有多少数 < k;

  一种朴素算法就是,暴力从区间 L 到 R 扫过去,每遇到小于k的位置加1

分块_第1张图片

没错,是不是这种方法很傻。但分块就是从这里优化过来的。

假设,我们有一个长度为 n 的序列,然后我们把他切成 sqrt(n)块,把每一块中的东西当成一个整体。(至于为什么块的大小是sqrt(n),自己可以百度一下QAQ)

几个术语:      完整块  被操作区间完全覆盖的块

                        不完整块 不完全覆盖的块

然后我们考虑怎么得出答案

  1.    首先我们要预处理每个点在哪个块,以及每个块的左右端点。
  2.    然后, 对于不完整的块,我们可以考虑直接暴力修改,因为元素比较少。
  3.   那么, 对于完整的块,我们就可以考虑打上lazy标记。

总而言之,分块就是将小块暴力,大块处理的一种方法。

然后这就可以用分块——这种看似高大上的方法做完。。。。。。比之前傻傻的暴力是不是好了很多。

分块_第2张图片

下面就是预处理代码

1     for(long long i = 1; i <= n; i++){
2         pos[i] = (i-1)/len+1;//每个点所在的块
3     }
4     for(int i = 1; i <= len; i++){
5         L[i] = (i-1)*len+1;//块的左右端点
6         R[i] = i*len;
7     }
8     if(R[len] < n) L[len+1] = R[len] +1; R[len+1] = n;//最后一个块单独处理
ycl

 

思想讲完了,下面就该看几道题了。

P4145 花神游历各国

题意大概是给出一个长为n的序列,以及n个操作,操作有区间开方和区间求和。

我们会发现开方操作比较难,主要是维护整块时,必须一个个的去修改,那这样岂不是和不同的暴力复杂度不就差不多了吗.

然后我们接着往深里想,不难发现,一个数经过几次开方后,就会变成0或1.

如果每次区间开方只要修改不完整的块,直接暴力修改就可以了。

如果修改一些整块,我们可以只要在每个整块暴力开方后,记录一下元素是否变成了0或1,修改时直接跳过全为0或1的块

就okk了 ,这样每个元素至多开方不超过4次,复杂度刚好可以。

 1 #include
 2 #include
 3 #include
 4 #include
 5 #include
 6 using namespace std;
 7 const int N = 1e5+100;
 8 long long n,m,k,l,r;
 9 long long a[N],pos[N],L[N],R[N],sum[N];
10 bool vis[N];
11 void check(long long x){//检查每个块是否全部变为0或1
12     if(vis[x]) return;
13     vis[x] = 1;
14     sum[x] = 0;
15     for(int i = L[x]; i <= R[x]; i++){
16         a[i] = sqrt(a[i]);
17         sum[x] += a[i];
18         if(a[i] > 1) vis[x] = 0;
19     }
20 }
21 void chenge(long long l,long long r){
22     int p = pos[l],q = pos[r];
23     if(p == q){//在一个块内,直接暴力修改
24         for(int i = l; i <= r; i++){
25             sum[p] -=a[i];
26             a[i] = sqrt(a[i]);
27             sum[p] += a[i];
28         }
29     } 
30     else {
31         for(int i = p+1; i <= q-1; i++) check(i);//修改整块
32         for(int i = l; i <= R[p]; i++){//左端点的散块
33             sum[p] -=a[i];
34             a[i] = sqrt(a[i]);
35             sum[p] += a[i];
36         }
37         for(int i = L[q]; i <= r; i++){//右端点的散块
38             sum[q] -= a[i];
39             a[i] = sqrt(a[i]);
40             sum[q] += a[i];
41         }
42     }
43 }
44 long long ask(long long l,long long r){//同上面的修改操作
45     long long ans = 0; 
46     int  p = pos[l],q = pos[r];
47     if(p == q) for(int i = l; i <= r; i++) ans += a[i];
48     else {
49         for(long long i = p+1; i <= q-1; i++) ans += sum[i];
50         for(int i =l; i <= R[p]; i++) ans+= a[i];
51         for(long long i  = L[q]; i <= r; i++) ans += a[i];
52     }
53     return ans;
54 }
55 int main(){
56     cin>>n;
57     memset(vis, false, sizeof(vis));
58     int len = sqrt(n);
59     for(long long i = 1; i <= n; i++) cin>>a[i];
60     for(long long i = 1; i <= n; i++){//预处理每个点所在的块
61         pos[i] = (i-1)/len+1;
62     }
63     for(int i = 1; i <= len; i++){每个块的左右端点
64         L[i] = (i-1)*len+1;
65         R[i] = i*len;
66     }
67     if(R[len] < n) L[len+1] = R[len] +1; R[len+1] = n;
68     for(int i = 1; i <= len+1; i++){//每个块的元素和
69         for(int j = L[i]; j <= R[i]; j++){
70             sum[i]+=a[j];
71         }
72     }
73     cin>>m;
74     for(long long i = 1; i <= m; i++){
75         cin>>k>>l>>r;
76         if(l > r) swap(l,r);
77         if( k == 0) chenge(l,r);
78         else cout<endl;
79     }
80     return 0;
81 }
代码

然后这道题可能分块没问题会得到90到100分,(到现在我也不知道自己分块哪里出问题了。。。。)

就像这样

分块_第3张图片

当然你也可以试试去写线段树,然后就会轻松AC了。emmmm最好也写写分块。

于是我不厚道的把线段树代码掏了出来

 1 #include
 2 #include
 3 #include
 4 #include
 5 using namespace std;
 6 const int N = 100010;
 7 long long n,m,opt,l,r;
 8 long long a[N];
 9 struct tree{
10     struct node{
11         int lc,rc;
12         long long tag,sum;//tag表示这一段区间的值是否全小于1 
13     }tr[N<<2];
14     #define l(o) tr[o].lc
15     #define r(o) tr[o].rc
16     #define tag(o) tr[o].tag
17     #define sum(o) tr[o].sum
18     void up(int o){
19         sum(o) = sum(o*2) + sum(o*2+1);
20         tag(o) = tag(o*2) & tag(o*2+1); 
21     }
22     void build(int o,int L,int R){
23         l(o) = L , r(o) = R;
24         if(L == R){
25             sum(o) = a[L];
26             if(a[L] <= 1) tag(o) = 1;
27             else tag(o) = 0;
28             return ;
29         }
30         int mid = (L + R) / 2;
31         build(o*2,L,mid);
32         build(o*2+1,mid+1,R);
33         up(o);
34     }
35     void chenge(int o,int L,int R){
36         if(tag(o) == 1) return;
37         if(l(o) == r(o)){
38             sum(o) = (int) sqrt(sum(o));
39             if(sum(o) <= 1) tag(o) = 1;
40             return ; 
41         }
42         int mid = (l(o) + r(o)) / 2;
43         if(L <= mid) chenge(o*2,L,R);
44         if(R > mid) chenge(o*2+1,L,R);
45         up(o);
46     }
47     long long ask(int o,int L,int R){
48         if(L <= l(o) && R >= r(o)){
49             return sum(o);
50         }
51         long long ans = 0;
52         int mid = (l(o) + r(o)) /2;
53         if(l <= mid) ans += ask(o*2,L,R);
54         if(R > mid) ans += ask(o*2+1,L,R);
55         return ans; 
56     }
57 }tree;
58 int main(){
59     scanf("%ld",&n);
60     for(int i = 1; i <= n; i++) scanf("%ld",&a[i]);
61     tree.build(1,1,n);
62     scanf("%ld",&m);
63     for(int i = 1; i <= m; i++){
64         scanf("%ld%ld%ld",&opt,&l,&r);
65         if(l > r) swap(l,r);
66         if(opt == 0){
67             tree.chenge(1,l,r);
68         }
69         else{
70             printf("%ld\n",tree.ask(1,l,r));
71         }
72     }
73     return 0;
74 }
线段树

 

第二题 P2801 教主的魔法

这个题大致就是区间加和区间询问小于k的数。

对于每个询问,可以先将这个块排好序,然后二分查找k的值就行了。散块直接暴力查询没有排序的原序列。

怎么修改呢???

对于整块,我们可以打上 lazy 标记,查询时直接查 >= k - lazy 的值;

对于散块,直接暴力修改,在直接排个序就okk了。

 

第N题:P4168 蒲公英  (这道题我也不会,贼恶心

这道题题意就是给你一段区间,然后询问区间的最小众数。

首先 a < 1e9先离散化;

然后预处理出两个数组 p[i][j] 表示第i个块到第j个块的最小众数。

                                    s[i][j] 前i个块中 j出现的次数

 如何处理呢

  1.  对于s, 直接每一个块扫一遍  。
  2.  对于 p ,双重枚举i,j开个数组暴力统计每个数出现多少次就行了。
  3. 对于询问操作,残块暴力搞,整块骚操作。所以对于posr - posl <= 2 的情况直接可以暴力搞出来

 4.对于其他的情况,答案要么是整块中的众数,或者残块中的数。对于残块中的数,我们已经预处理了之前数字出现的前缀和,

        所以能很快的求出这个数出现的次数。

       5.对于整块中的众数,我们也可以很快的求出ta在残块中的次数。这样答案就可以出来了。

        

void Treaker()//分块预处理 
{
    for(int i=1;i<=n;++i)
    for(int j=pos[i];j<=pos[n];++j)s[j][a[i]]++;
    
    for(int i=1;i<=pos[n];++i)
    {
        memset(tong,0,sizeof(tong));
        for(int j=i;j<=pos[n];++j)
        {
            p[i][j]=p[i][j-1];
            for(int k=L[j];k<=R[j];++k)
            {
                tong[a[k]]++;
                if((tong[a[k]]>tong[p[i][j]])||(tong[a[k]]==tong[p[i][j]]&&a[k]a[k];
            }
        }
    }
    memset(tong,0,sizeof(tong));
}
ycl

比较重要的是

p[i][j]=p[i][j-1]

因为我们需要先继承先前的区间众数,然后才能更新,否则的话就会听取 WA 声一片(据说我们学长神犇就卡在这里好几回

if(pos[r]-pos[l]<=2)
    {
        int res=0;
        for(int i=l;i<=r;++i)
        {
            ++tong[a[i]];
            if(tong[a[i]]>tong[res]||(tong[a[i]]==tong[res]&&a[i]a[i];
        }
        for(int i=l;i<=r;++i)tong[a[i]]=0;
        return res;
    }
小于等于2的情况
int res=0;
    for(int i=l;i<=R[pos[l]];++i)if(!tong[a[i]])tong[a[i]]+=s[pos[r]-1][a[i]]-s[pos[l]][a[i]];
    for(int i=L[pos[r]];i<=r;++i)if(!tong[a[i]])tong[a[i]]+=s[pos[r]-1][a[i]]-s[pos[l]][a[i]];
    for(int i=l;i<=R[pos[l]];++i)
    {
        ++tong[a[i]];
        if(tong[a[i]]>tong[res]||(tong[a[i]]==tong[res]&&a[i]a[i];
    }
    for(int i=L[pos[r]];i<=r;++i)
    {
        ++tong[a[i]];
        if(tong[a[i]]>tong[res]||(tong[a[i]]==tong[res]&&a[i]a[i];
    }
    int k=p[pos[l]+1][pos[r]-1];
    int lin=s[pos[r]-1][k]-s[pos[l]][k];
    for(int i=l;i<=R[pos[l]];++i)lin+=(a[i]==k);
    for(int i=L[pos[r]];i<=r;++i)lin+=(a[i]==k);
    if(lin>tong[res]||(lin==tong[res]&&kk;
    
    for(int i=l;i<=R[pos[l]];++i)tong[a[i]]=0;
    for(int i=L[pos[r]];i<=r;++i)tong[a[i]]=0;
    return res;
其他情况

最后有几点要注意

  1. 注意离散化,否则直接RE到原地起飞。
  2. 注意 p 数组的转移,否则 WA 到怀疑人生。
  3. 注意分块的边界,否则TLE 到直接原地爆炸。

由于本蒟蒻水平太差,此篇题解参考了神犇(wjlss)的博客,如果有人看不懂的话,这里有链接QAQ  wljss

最后本人代码一直炸,所以附上神犇的代码

#include
#include
#include
#include
#include
using namespace std;
int n,m,tot,len,l,r,ans;
const int N=40010,M=50010,K=510;
int a[N],b[N],pos[N],s[K][N],L[K],R[K],p[K][K],tong[N];
int read()
{
    char ch;int x=0,f=1;
    while(!isdigit(ch=getchar()))
    {(ch=='-')&&(f=-f);}
    while(isdigit(ch))
    {x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
    return x*f;
}
int cnt=0;
void yych()//离散化 
{
    len=sqrt(n);
    memset(L,0x3f,sizeof(L));
    for(int i=1;i<=n;++i)
    {
        pos[i]=(i-1)/len+1;
        L[pos[i]]=min(L[pos[i]],i);
        R[pos[i]]=max(R[pos[i]],i);
        a[i]=read();
        b[i]=a[i];  
    }
    sort(b+1,b+1+n);
    tot=unique(b+1,b+1+n)-b-1;
    for(int i=1;i<=n;++i)a[i]=lower_bound(b+1,b+1+tot,a[i])-b;
}
void Treaker()//分块预处理 
{
    for(int i=1;i<=n;++i)
    for(int j=pos[i];j<=pos[n];++j)s[j][a[i]]++;

    for(int i=1;i<=pos[n];++i)
    {
        memset(tong,0,sizeof(tong));
        for(int j=i;j<=pos[n];++j)
        {
            p[i][j]=p[i][j-1];
            for(int k=L[j];k<=R[j];++k)
            {
                tong[a[k]]++;
                if((tong[a[k]]>tong[p[i][j]])||(tong[a[k]]==tong[p[i][j]]&&a[k]a[k];
            }
        }
    }
    memset(tong,0,sizeof(tong));
}
int ask(int l,int r)
{
    if(pos[r]-pos[l]<=2)
    {
        int res=0;
        for(int i=l;i<=r;++i)
        {
            ++tong[a[i]];
            if(tong[a[i]]>tong[res]||(tong[a[i]]==tong[res]&&a[i]a[i];
        }
        for(int i=l;i<=r;++i)tong[a[i]]=0;
        return res;
    }
    int res=0;
    for(int i=l;i<=R[pos[l]];++i)if(!tong[a[i]])tong[a[i]]+=s[pos[r]-1][a[i]]-s[pos[l]][a[i]];
    for(int i=L[pos[r]];i<=r;++i)if(!tong[a[i]])tong[a[i]]+=s[pos[r]-1][a[i]]-s[pos[l]][a[i]];
    for(int i=l;i<=R[pos[l]];++i)
    {
        ++tong[a[i]];
        if(tong[a[i]]>tong[res]||(tong[a[i]]==tong[res]&&a[i]a[i];
    }
    for(int i=L[pos[r]];i<=r;++i)
    {
        ++tong[a[i]];
        if(tong[a[i]]>tong[res]||(tong[a[i]]==tong[res]&&a[i]a[i];
    }
    int k=p[pos[l]+1][pos[r]-1];
    int lin=s[pos[r]-1][k]-s[pos[l]][k];
    for(int i=l;i<=R[pos[l]];++i)lin+=(a[i]==k);
    for(int i=L[pos[r]];i<=r;++i)lin+=(a[i]==k);
    if(lin>tong[res]||(lin==tong[res]&&kk;

    for(int i=l;i<=R[pos[l]];++i)tong[a[i]]=0;
    for(int i=L[pos[r]];i<=r;++i)tong[a[i]]=0;
    return res;
}
int main()
{
    cin>>n>>m;
    yych();
    Treaker();
    for(int i=1;i<=m;++i)
    {
        l=read();r=read();
        l=(l+ans-1)%n+1;r=(r+ans-1)%n+1;
        if(l>r)swap(l,r);
        ans=b[ask(l,r)];
        printf("%d\n",ans);
    }
    return 0;
}
神犇完整代码

然后你就可以AC 一道紫题(据说前几年这个还是一道黑题

最后,分块的东西也就这么多,主要理解小块暴力,整块瞎搞就可以了。

最后附上习题。

  1. 作诗
  2. 蒲公英
  3. 线段树2
  4. 区间众数强制在线(黑题,难度比较高,有能力的可以做做)
  5. 定价 (河北15年省选题,思考出怎么分块就可以轻松AC了)

 

 

 

 

 

 

 

你可能感兴趣的:(分块)