淺談分塊算法經典問題&優化

首先,咱們知道,分塊是一種優雅的暴力,他能夠很靈活的變通,下面,我整理了幾道分塊的經典題目跟你們分享(持續更新中):  c++

1.區間加,區間求小於 k 的數的個數。git

這是分塊裏比較經典的一道題目了。首先看區間加,咱們把序列分紅 n/S 個塊(S爲塊長),對每一個不完整的塊(角塊),直接暴力加,由於塊長爲 S,因此這裏的複雜度是 O(S),不會太大,而後咱們把每一個完整的區間,打上一個tag,這個tag裏邊記錄的是這個整塊一共加了多少,這樣,複雜度最壞就是 O(n/S),爲了均衡,因此通常咱們的塊長取 √n。oop

再就是區間求小於個數,一開始咱們確定沒啥想法,可是你們確定都用過二分吧,在原本有序的序列裏邊,咱們能夠二分查找一下這個個數,這樣就能夠作到log的時間複雜度求出整塊小於 k 的個數了。可是好像直接二分彷佛麻煩了一些,這時候就能夠請出萬能的STL的。優化

在STL庫的vector中,咱們能夠這樣查找小於 k 的數的個數this

vector<int>v;
//do something...
v.lower_bound(v.begin(),v.end(),k)

這裏邊用到了一個東西叫lower_bound,這是個好東西,能夠在log時間複雜度內求區間小於 k 的數的個數,因此咱們就想,是否是也能夠將每一個塊放進這樣一個vector裏邊,對每一個塊進行排序(複雜度O(nlogn)),而後仍是那個思路,角塊直接暴力統計,對於每一個完整的塊,用lower_bound二分一下(k-這塊的tag)這個值,就能夠在單次 O(S*logS) 的複雜度內求出小於 k 的個數。spa

因此咱們不得不感嘆,STL大法好\(^o^)/~code

可是有個小問題,咱們有可能在角塊加的時候,使本來有序的一塊變得亂序,及時不是亂序的,塊裏邊的數也改變了,因而咱們還得加一個步驟,就是整塊重構。blog

重構其實也很簡單,就是直接將這個塊清空,而後把修改事後的數放進原這塊的vector,而後排個序,就完事了。排序

最後分析一下複雜度 :get

     預處理  nlogS
     區間加    S
區間小於k的個數 SlogS

因此整體的複雜度是 O(nlogS+mSlogS)

S取 sqrtn 或 n/m

上代碼:  

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=5e4+5;
inline int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
    return x*f;
}
inline void print(int x)
{
     if(x<0) putchar('-'),x=-x;
     if(x>9) print(x/10);
     putchar(x%10+'0');
}
int size;int belong[maxn],a[maxn],taga[maxn];
vector<int>s[505];
int n;
inline void reconstruct(int pos)
{
    s[pos].clear();
    for(int i=(pos-1)*size+1;i<=min(size*pos,n);i++)s[pos].push_back(a[i]);
    sort(s[pos].begin(),s[pos].end());
}
inline void add(int l,int r,int k)
{
    for(int i=l;i<=min(belong[l]*size,r);i++)a[i]+=k;
    reconstruct(belong[l]);
    if(belong[l]!=belong[r])
    {
        for(int i=(belong[r]-1)*size+1;i<=r;i++)a[i]+=k;
        reconstruct(belong[r]);
    }
    for(int i=belong[l]+1;i<=belong[r]-1;i++)taga[i]+=k;
}
inline int search(int l,int r,int k)
{
    int ans=0;
    for(int i=l;i<=min(belong[l]*size,r);i++)if(a[i]+taga[belong[i]]<k)ans++;
    if(belong[l]!=belong[r])
    {
        for(int i=(belong[r]-1)*size+1;i<=r;i++)if(a[i]+taga[belong[i]]<k)ans++;
    }
    for(int i=belong[l]+1;i<=belong[r]-1;i++)
    {
        int x=k-taga[i];
        ans+=lower_bound(s[i].begin(),s[i].end(),x)-s[i].begin();
    }
    return ans;
}
int main()
{
    n=read();
    for(int i=1;i<=n;i++)a[i]=read();
    size=sqrt(n);
    for(int i=1;i<=n;i++)belong[i]=(i-1)/size+1,s[belong[i]].push_back(a[i]);
    for(int i=1;i<=belong[n];i++)sort(s[i].begin(),s[i].end());
    for(int i=1;i<=n;i++)
    {
        int cz,l,r,k;
        cz=read();l=read();r=read();k=read();
        if(cz==0)add(l,r,k);
        if(cz==1)print(search(l,r,k*k)),putchar('\n');
    } 
    return 0;
}

給出題目連接——loj分塊入門2

2.區間加,區間第k小

看到第 k 小,咱們首先想到的是動態開點線段樹——主席樹,可是看到區間加,主席樹就熄火了,平衡樹彷佛也很難作到,因而咱們考慮萬能的分塊。

這一題與上一題相似,咱們能夠把這些操做也放在vector裏邊。咱們將每塊對應的數放進一個vector並將每一塊排序,區間加直接整塊打tag,角塊每一個數暴力加並從新排序。區間第k小,咱們考慮二分套二分:第 k 小就在這個序列中比這個數小的個數有 k 個,

因而咱們在外圍二分一個數值,內部用上一題的方法統計比傳進來的這個數值小的個數,最後直接輸出就OK了,具體看代碼。

#pragma GCC optimize("O3")
#pragma GCC optimize("Ofast","-funroll-loops","-fdelete-null-pointer-checks")
#pragma GCC target("ssse3","sse3","sse2","sse","avx2","avx")
#include<bits/stdc++.h>
#define size block
typedef long long ll;
using namespace std;
const int maxn=1e5+5;
const ll Inf=9999999999;
struct istream{
    char buf[23333333],*s;
    inline istream(){
        buf[fread(s=buf,1,23333330,stdin)]='\n';
        fclose(stdin);
    }
    inline istream&operator>>(int&d){
        d=0;
        for(;!isdigit(*s);++s);
        while(isdigit(*s))
        d=(d<<3)+(d<<1)+(*s++^'0');
        return*this;
    }
}read;
/*inline void print(int x)
{
    if(x==0){putchar('0');return;}if(x<0)putchar('-'),x=-x;
    int len=0,buf[15];while(x)buf[len++]=x%10,x/=10;
    for(register int i=len-1;i>=0;i--)putchar(buf[i]+'0');return;
}*/
struct ostream{
    char buf[8000005],*s;
    inline ostream(){s=buf;}
    inline ostream&operator<<(int d){
        if(!d){
            *s++='0';
        }else{
            static int w;
            for(w=1;w<=d;w*=10);
            for(;w/=10;d%=w)*s++=d/w^'0';
        }
        return*this;
    }
    inline ostream&operator<<(const char&c){*s++=c;return*this;}
    inline void flush(){
        fwrite(buf,1,s-buf,stdout);
        s=buf;
    }
    inline~ostream(){flush();}
}print;
int a[maxn],belong[maxn],tag[maxn];
vector<int>s[505];
int block;int n,m;
inline void reduce(int pos)
{
    s[pos].clear();
    for(register int i=(pos-1)*size+1;i<=min(pos*size,n);i++)s[pos].push_back(a[i]);
    sort(s[pos].begin(),s[pos].end());
}
inline void add(int l,int r,int k)
{
    for(register int  i=l;i<=min(belong[l]*size,r);i++)a[i]+=k;
    reduce(belong[l]);
    if(belong[l]!=belong[r])
    {
        for(register int i=(belong[r]-1)*size+1;i<=r;i++)a[i]+=k;
        reduce(belong[r]);
    }
    for(register int i=belong[l]+1;i<=belong[r]-1;i++)tag[i]+=k;
}
inline bool _find(int l,int r,int sum,int emm)
{
    int ans=0;
    for(register int i=l;i<=min(belong[l]*size,r);i++)if(a[i]+tag[belong[l]]<sum)ans++;
    if(belong[l]!=belong[r])
    {
        for(register int i=(belong[r]-1)*size+1;i<=r;i++)if(a[i]+tag[belong[r]]<sum)ans++;
    }
    for(register int i=belong[l]+1;i<=belong[r]-1;i++)
    {
        int x=sum-tag[i];
        ans+=lower_bound(s[i].begin(),s[i].end(),x)-s[i].begin();
    }
    return ans<emm?true:false;
}
inline ll get(int l,int r,bool x)
{
    ll m=x?Inf:-Inf;
    for(register int i=l;i<=min(belong[l]*size,r);i++)m=x?min(m,(ll)a[i]+tag[belong[l]]):max(m,(ll)a[i]+tag[belong[l]]);
    if(belong[l]!=belong[r])
    {
        for(register int i=(belong[r]-1)*size+1;i<=r;i++)m=x?min(m,(ll)a[i]+tag[belong[r]]):max(m,(ll)a[i]+tag[belong[r]]);
    }
    for(register int i=belong[l]+1;i<=belong[r]-1;i++)
    {
        m=x?min(m,(ll)s[i][0]+tag[i]):max(m,(ll)s[i][size-1]+tag[i]);
    }
    return m;
}
inline ll search(int l,int r,int k)
{
    ll m=get(l,r,1),n=get(l,r,0);
    while(m<n)
    {
        ll mid=((m+n)>>1)+1ll;
        if(_find(l,r,mid,k))m=mid;
        else n=mid-1;
    }
    return m;
}
int main()
{
    read>>n>>m;block=(int)1.0*sqrt(n)*log(n);
    for(register int i=1;i<=n;i++)read>>a[i];
    for(register int i=1;i<=n;i++)belong[i]=(i-1)/size+1,s[belong[i]].push_back(a[i]);
    for(register int i=1;i<=belong[n];i++)sort(s[i].begin(),s[i].end());
    for(register int i=1;i<=m;i++)
    {
        int cz,l,r,k;
        read>>cz>>l>>r>>k;
        if(cz==1){int p=search(l,r,k);print<<p<<'\n';}
        else add(l,r,k);
    }
}

 

 可是這樣很暴力,複雜度大概是 O(n√nlog2n)的,因此咱們考慮優化。

首先上一句名言:一杯茶,一包煙,一個塊長調一天。

祝大家調塊長順利昂(

塊長大概想我上面那樣,取 logn*sqrt(n) 是最優的。

第二就是,重排的時候,能夠用歸併排序,時間大概是線性的。

第三,兩個邊零散點二分的時候,用歸併排序講和成一個塊,這樣就能夠用log^2的時間複雜度求出。

代碼等我碼出來以後再給。

給出題目連接——

P5356 [Ynoi2017]由乃打撲克

相關文章
相關標籤/搜索