• 分块


                                 分块

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

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

    首先,我们考虑一道题。

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

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

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

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

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

                            不完整块 不完全覆盖的块

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

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

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

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

    下面就是预处理代码

     

    1 for(int i = 1; i <= n; i++){
    2     scanf("%d",&a[i]);
    3     pos[i] = (i-1)/len +1;//每个点所在的块
    4     L[pos[i]] = 2333333;
    5 }
    6 for(int i = 1; i <= n; i++){
    7       L[pos[i]] = min(L[pos[i]],i);//每个块的左右端点
    8       R[pos[i]] = max(R[pos[i]],i);
    9 }

     一个比较重要的问题就是,块的数量不一定是len+1

    他正确的是n/len+1

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

    P4145 花神游历各国

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

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

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

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

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

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

    下面是代码;

     1 #include<iostream>
     2 #include<cstdio>
     3 #include<algorithm>
     4 #include<cmath>
     5 #include<cstring>
     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         L[pos[i]] = 23333333;
    63     }
    64     for(long long i = 1; i <= n; i++){
    65         L[pos[i]] = min(L[pos[i]],i);
    66         R[pos[i]] = max(R[pos[i]],i);
    67     }
    68     for(int i = 1; i <= n/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<<ask(l,r)<<endl;
    79     }
    80     return 0;
    81 }

    关于这道题,你还可以用线段树做,并且要比分块快QAQ。

     1 #include<iostream>
     2 #include<cstdio>
     3 #include<algorithm>
     4 #include<cmath>
     5 #include<cstring>
     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){
    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         L[pos[i]] = 23333333;
    63     }
    64     for(long long i = 1; i <= n; i++){
    65         L[pos[i]] = min(L[pos[i]],i);
    66         R[pos[i]] = max(R[pos[i]],i);
    67     }
    68     for(int i = 1; i <= n/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<<ask(l,r)<<endl;
    

    第二题 P2801 教主的魔法

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

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

    怎么修改呢???

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

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

     

    例三 zcl的妹

    例三 ZCL的妹子

      1 #include<iostream>
      2 #include<cstdio>
      3 #include<algorithm>
      4 #include<cmath>
      5 using namespace std;
      6 const int N = 1e6+10;
      7 #define LL long long
      8 char opt;
      9 int n,m,x,y,len;
     10 int L[N],R[N];
     11 LL sum[N],size[N],a[N],pos[N],cnt[N];
     12 inline int read()
     13 {
     14     int s = 0, w = 1; char ch = getchar();
     15     while(ch < '0' || ch > '9'){if(ch == '-') w = -1;ch = getchar();}
     16     while(ch >= '0'&&ch <= '9'){s = s * 10 +ch -'0'; ch = getchar();}
     17     return s * w;
     18 }
     19 void chenge1(int x,int y)
     20 {
     21     if(!cnt[x]) return ;
     22     int xx = pos[x];
     23     sum[xx] -= y;
     24     a[x] -= y;
     25 }
     26 void chenge2(int x,int y)
     27 {
     28     int xx = pos[x];
     29     if(cnt[x] == 1)
     30     {
     31         sum[xx] -= a[x];
     32         a[x] = y;
     33         sum[xx] += a[x];
     34      }
     35      else{
     36          cnt[x] = 1;
     37          sum[x] -= a[x];
     38          a[x] = y;
     39          size[xx]++;
     40          sum[xx] += a[x];
     41      }
     42 }
     43 void chenge3(int x)
     44 {
     45      int p = 1;
     46      while(x - size[p] > 0) x -= size[p], p++;
     47      for(int i = L[p]; i <= R[p]; i++)
     48      {
     49          x -= cnt[i];
     50          if(x == 0)
     51          {
     52              size[p]--;
     53              sum[p] -= a[i];
     54              cnt[i] = a[i] = 0;
     55              return ;
     56          }
     57      } 
     58 }
     59 LL ask()
     60 {
     61     LL ans = 0;
     62     for(int i = 1; i <= pos[500000]; i++) ans += sum[i];
     63     return ans;
     64 }
     65 int main()
     66 {
     67     n = read(); m = read();
     68     len = sqrt(500000);
     69     for(int i = 1; i <= n; i++) a[i] = read(),cnt[i] = 1;
     70     for(int i = 1; i <= 500000; i++)
     71     {
     72         pos[i] = (i-1)/len+1;
     73         L[pos[i]] = 23333333;
     74     }
     75     for(int i = 1; i <= 500000; i++)
     76     {
     77         size[pos[i]] += cnt[i];
     78         L[pos[i]] = min(L[pos[i]] , i);
     79         R[pos[i]] = max(R[pos[i]] , i);
     80         sum[pos[i]] += a[i]; 
     81     }
     82     for(int i = 1; i <= m; i++)
     83     {
     84         cin>>opt;
     85         if(opt == 'C')
     86         {
     87             x = read(); y = read();
     88             chenge1(x,y);
     89         }
     90         if(opt == 'I')
     91         {
     92             x = read(); y = read();
     93             chenge2(x,y);
     94         }
     95         if(opt == 'D')
     96         {
     97             x = read();
     98             chenge3(x);
     99         }
    100         if(opt == 'Q') printf("%lld\n",ask());
    101     }
    102     return 0;
    103 }
    View Code

    第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]<p[i][j]))p[i][j]=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]<res))res=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]<res))res=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]<res))res=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]&&k<res))res=k;
        
        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<algorithm>
    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<cmath>
    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]<p[i][j]))p[i][j]=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]<res))res=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]<res))res=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]<res))res=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]&&k<res))res=k;
    
        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. Karen and coffee
    2. 蒲公英
    3. 线段树2
    4. 定价 (河北15年省选题,思考出怎么分块就可以轻松AC了)

     

  • 相关阅读:
    个人总结21
    个人总结08
    个人总结07
    构建之法读后感01
    学习进度表 03
    四则运算3
    求最大值
    学习进度表02
    四则运算 2
    学习进度表01
  • 原文地址:https://www.cnblogs.com/genshy/p/13281287.html
Copyright © 2020-2023  润新知