關於分塊的我的理解(一)

  上週開始講分塊,然而和我想的不同,你們都聽得很懵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++!!

相關文章
相關標籤/搜索