1. 程式人生 > >分塊(真的是優雅的暴力)

分塊(真的是優雅的暴力)

M - 一道普通題1

給出一個長為 n​n​ 的數列,以及 n​n​ 個操作,操作涉及區間加法,詢問區間內小於某個值 x​x​ 的前驅(比其小的最大元素)。

Input

第一行輸入一個數字 n,1≤n≤100000n,1≤n≤100000。

第二行輸入 nn 個非負整數,第 ii個數字為 ai(0≤ai≤109)ai(0≤ai≤109)以空格隔開。

接下來輸入nn 行詢問,每行輸入四個數字 opt、l、r、c(c>0)opt、l、r、c(c>0),以空格隔開。

若 oopt=0opt=0,表示將位於[l,r][l,r]的之間的數字都加 cc。

若 opt=1opt=1,表示詢問[l,r][l,r]中 cc 的前驅的值(不存在則輸出 −1−1)。

Output

對於每次詢問,輸出一行一個數字表示答案。

保證所有資料在int範圍內

Sample Input

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

Sample Output

3
-1

Hint

最初一看到這個題的這種格式就準備用樹狀陣列來做的,可是做到詢問前驅的時候就發現做不好了,然後就瞄了一下了學長的題解,才發現要用分塊做,分塊呢我只知道它的模板,~~~(我真的好菜哎^^),然後我就套模板一寫,可提交就T了,中間又錯了好多次,最後又看了看學長的程式碼,學長是用的vector來存每一塊的數,左右兩端殘塊暴力處理,中間的整塊(如果有的話)一塊塊的處理,這時更新的時候就沒有對原陣列直接+val,而是維護一個lazy陣列,表示每一塊要加的數是多少,比較的時候用val-lazy[i]與原陣列比較,如果有比它小的最大的數則存在字首,且為找到的原陣列的那個數+lazy[i],否則就沒有。。。。。。。最後明白這才叫分塊嘛,中間一塊一塊的處理,像我最初中間還是一個一個點處理,那分塊意義何在。。。。。。。

AC程式碼:

#include<iostream>
#include<cstring>
#include<algorithm>
#include<set>
#include<cmath>
#include<cstdio>
#include<vector>
using namespace std;
const int maxn=100001;
int belong[maxn],l[maxn],r[maxn],b[maxn];
int lazy[maxn];
int block,num;
int n;
vector<int>s[1010];
void build()
{
    block=(int)sqrt(n);
    num=n/block;
    if(n%block) num++;
    for(int i=1;i<=num;i++)
        l[i]=(i-1)*block+1,r[i]=i*block;
    r[num]=n;//
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&b[i]);
        belong[i]=(i-1)/block+1;
        s[belong[i]].push_back(b[i]);
    }
    for(int i=1;i<=belong[n];i++) sort(s[i].begin(),s[i].end());
}
void update(int ll,int rr,int val)
{
    int first=belong[ll];
    int last=belong[rr];
    for(int i=ll;i<=min(belong[ll]*block,rr);i++)
        b[i]+=val;
    s[belong[ll]].clear();
    for(int i=(belong[ll]-1)*block+1;i<=min(belong[ll]*block,n);i++)
        s[belong[ll]].push_back(b[i]);
    sort(s[belong[ll]].begin(),s[belong[ll]].end());//左邊殘塊

    if(belong[ll]==belong[rr]) return;//如果只有一個殘塊修改完畢

    for(int i=(belong[rr]-1)*block;i<=rr;i++)
        b[i]+=val;
    s[belong[rr]].clear();
    for(int i=(belong[rr]-1)*block+1;i<=min(belong[rr]*block,n);i++)
        s[belong[rr]].push_back(b[i]);
    sort(s[belong[rr]].begin(),s[belong[rr]].end());//右邊殘塊

    for(int i=first+1;i<=last-1;i++)
        lazy[i]+=val;//中間的整塊用lazy維護而不對原陣列直接+
}
int query(int ll,int rr,int val)
{
    int first=belong[ll];
    int last=belong[rr];
    int _max=-1;
    for(int i=ll;i<=min(r[belong[ll]],rr);i++)
        if(b[i]+lazy[belong[i]]<val)
            _max=max(_max,b[i]+lazy[belong[i]]);
    if(first!=last)
    {
        for(int i=l[belong[rr]];i<=rr;i++)
            if(b[i]+lazy[belong[i]]<val)
              _max=max(b[i]+lazy[belong[i]],_max);
    }
    for(int i=first+1;i<=last-1;i++)
    {
        auto it=lower_bound(s[i].begin(),s[i].end(),val-lazy[i]);
        if(it==s[i].begin()) continue;
        --it;
        _max=max(_max,*it+lazy[i]);
    }
    return _max;
}
int main()
{
    //ios::sync_with_stdio(0),cin.tie(0);
    while(~scanf("%d",&n)){
    build();
    int m=n;
    int op,x,y,k;
    while(m--)
    {
        scanf("%d%d%d%d",&op,&x,&y,&k);
        if(op==0)
        {
            update(x,y,k);
        }
        else if(op==1)
        {
            printf("%d\n",query(x,y,k));
        }
    }
    }
    return 0;
}

最後貼個模板:

分塊模板:
belong[]陣列表示第i個點屬於哪個塊,l[],r[]分別表示第i個點所在的左右邊界
block表示每塊的大小,num表示有多少塊
void build()
{
    block=(int)sqrt(n);
    num=n/block;
    if(n%block) num++;
    for(int i=1;i<=num;i++)
        l[i]=(i-1)*block+1,r[i]=i*block;
    r[num]=n;//最後一塊的邊界一定是n
    for(int i=1;i<=n;i++){
        cin>>a[i];
        belong[i]=(i-1)/block+1;
    }
    //下面維護需要的陣列.....
}