关于分块的我的理解(一)

  上周开始讲分块,然而和我想的不同,你们都听得很懵13 --> 以致于我对于本身对于分块的理解产生了怀疑。ios

  结果天然是写一篇blog自我检验一下了啊。算法

  那么切入正题。数组

分块

简意

  将一段暴力很费事的区间拆分红数个小区间分开求解,并采起预处理的方式下降复杂度。spa

  这种算法真的是很好的啊。code

  尤为是对于咱们这种懒惰oier来讲,简直就是福音啊。blog

实现方法

  将一段长序列分红相同的块大小,事先将整块的数据完成整理。而后进行整散拆分,整块直接使用,散块暴力get

  那么这又有什么没法理解的地方呢?我真的很不理解QAQstring

  那么接下来咱们采起例题的方式来更具象的理解一下吧。it

 基础. luogu P3870

题目描述:

  现有N(2 ≤ N ≤ 100000)盏灯排成一排,从左到右依次编号为:1,2,......,N。io

  而后依次执行M(1 ≤ M ≤ 100000)项操做,操做分为两种:

  第一种操做指定一个区间[a, b],而后改变编号在这个区间内的灯的状态(把开着的灯关上,关着的灯打开)。

  第二种操做是指定一个区间[a, b],要求你输出这个区间内有多少盏灯是打开的。灯在初始时都是关着的。

输入:

  第一行有两个整数N和M,分别表示灯的数目和操做的数目。

  接下来有M行,每行有三个整数,依次为:c, a, b。其中c表示操做的种类:

    当c的值为0时,表示是第一种操做。

    当c的值为1时表示是第二种操做。

  a和b则分别表示了操做区间的左右边界(1 ≤ a ≤ b ≤ N)。

输出:

  每当遇到第二种操做时,输出一行,包含一个整数:此时在查询的区间中打开的灯的数目。

样例输入:

4 5
0 1 2
0 2 4
1 2 3
0 2 4
1 1 4

样例输出:

1
2

解法:

  这种东西真的很显然了啊,我都说了是分块的裸题了啊QAQ

  咱们在修改的时候对于散块直接暴力修改,整块把用来标记的数组异或一下 1 就好了,修改次数为偶数时灯天然就不变了啊。

  但这样的话很差查整块,因此须要维护一个ans[i],用于保存块内目前开着的灯的数量。

  散块修改的时候好维护,而整块修改的时候把用块的大小减去ans维护就好了。

  那么查询的时候天然也很显然了啊,散块暴力,整块ans。

代码奉上:

  风格较丑,请见谅哈。

#include<iostream>
#include<cstring>
#include<cmath>
#include<cstdio>
#include<algorithm>
#include<queue>
#define rint register int
#define maxn 200010
using namespace std;

inline int read() {
    int s=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){ch=getchar();if(ch=='-')f=-1;}
    while(ch>='0'&&ch<='9')s=(s<<3)+(s<<1)+(ch^48),ch=getchar();
    return s*f;
}

int n,m,blo,o;
int bl[maxn],v[maxn],t[maxn],atag[maxn],sum[maxn];
int L[maxn],R[maxn];

inline void change(int l,int r) {
    if(bl[l]==bl[r]) {
        for(rint i=l;i<=r;++i) {
            if(v[i]) {
                v[i]=0;
                --sum[bl[l]];
            }
            else {
                v[i]=1;
                ++sum[bl[l]];
            }
        } 
        return;
    }
    for(rint i=l;i<=R[bl[l]];++i) {
        if(v[i]) {
            v[i]=0;
            --sum[bl[l]]; 
        }
        else {
            v[i]=1;
            ++sum[bl[l]];
        }
    }
    for(rint i=L[bl[r]];i<=r;++i) {
        if(v[i]) {
            v[i]=0;
            --sum[bl[r]];
        }
        else {
            v[i]=1;
            ++sum[bl[r]];
        }
    }
    for(rint i=bl[l]+1;i<=bl[r]-1;++i) 
        atag[i]=1-atag[i];
}

inline int query(int l,int r) {
    int ans=0;
    if(bl[l]==bl[r]) {
        for(rint i=l;i<=r;++i) 
            if(v[i]^atag[bl[l]]) 
                ++ans;
        return ans;
    }
    for(int i=l;i<=R[bl[l]];++i) 
        if(v[i]^atag[bl[l]]) 
            ++ans;
    for(int i=L[bl[r]];i<=r;++i) 
        if(v[i]^atag[bl[r]]) 
            ++ans;
    for(int i=bl[l]+1;i<=bl[r]-1;++i) {
        if(atag[i]) 
            ans+=(R[i]-L[i]+1)-sum[i];
        else 
            ans+=sum[i];
    }        
    return ans;
}

int main()
{
    n=read();
    m=read();
    blo=sqrt(n);
    n%blo?(o=n/blo+1):(o=n/blo);
    for(rint i=1;i<=n;++i) {
        v[i]=0;
        bl[i]=(i-1)/blo+1;
        if(v[i]) 
            ++sum[bl[i]];
    }
    for(rint i=1;i<=o;++i) {
        L[i]=(i-1)*blo+1;
        R[i]=i*blo;
    }
    while(m--) {
        int p=read();
        int l=read();
        int r=read();
        if(p==0) 
            change(l,r);
        if(p==1) 
            cout<<query(l,r)<<endl;
    }
    return 0;
} 

如此这题就完了耶~

 

 

 

此处有彩蛋哦~

  做为一个懒 的打题的人怎么能放过双倍经验呢??????

  P2846 [USACO08NOV]光开关Light Switching

  SP7259 LITE - Light Switching

祝你们RP++!!

相关文章
相关标签/搜索