1. 程式人生 > >分塊——優化的暴力

分塊——優化的暴力

def else 查表 color 之間 而且 優化 初始 得到

對於很多的題目,我們都可以找到n^2的暴力算法。

但是,當n在10000到200000之間的時候,n^2基本穩穩卡掉。

發現,這樣的題目,經常還與區間有關系的時候,可以考慮分塊做法。

分塊,顧名思義,就是把待處理的整個大區間分成若幹塊。

口訣是:塊外暴力,塊內查表。

那麽這個塊的大小應該怎麽分呢??

應該是sqrt(n)大小。

證明:*&%&*^$#()&^%#$^&#$(^$%(並不會證)

自我感覺,分塊很巧妙的把各種復雜度都向sqrt(N)靠近

發現很多的題,都是時間復雜度n根號n,空間復雜度也是n根號n

而且不管是什麽操作,基本上都是根號n。既沒有n^2,也不存在O(1)

感覺,巧妙地把復雜度平均了一下。

分塊雖然是暴力,但是是一種非常有水平的暴力。

關鍵是狀態的設計,怎樣達到塊外暴力,塊內查表。

大概的感覺是,都要圍繞塊來進行設計。

而且,通常要有兩個以上函數狀態有機配合。。。

例題1:BZOJ 4241歷史研究

Description:

一句話題意:求區間加權眾數。

Solution:

f[i][j]表示從第i塊開頭到j的最大值 (查詢的時候,塊內查表)
cnt[i][j]表示從第i塊開始到序列末尾j出現了多少次(便於暴力中的後綴差分)
邊角余料處理一下就好啦~

Code:

一般情況下,對於塊的左右端點有兩種求法。

1.lower_bound(blo+1,blo+n+1,blo[x])塊的左端點。

lower_bound(blo+1,blo+n+1,blo[x]+1)塊的右端點+1

2.處理塊的時候,直接結構體記錄塊的左右端點。(推薦)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=100050;
const int B=333;
ll f[B][N],cnt[B][N];
int a[N],b[N],tot;
int num[N],sta[N],top;
int blo[N],BLO;
int n,m;
ll ans;
int main()
{
    scanf("%d%d
",&n,&m); BLO=sqrt(n); for(int i=1;i<=n;i++)scanf("%d",&a[i]),blo[i]=(i-1)/BLO+1,b[i]=a[i]; sort(b+1,b+n+1);tot=unique(b+1,b+n+1)-b-1; for(int i=1;i<=n;i++)a[i]=lower_bound(b+1,b+n+1,a[i])-b; for(int i=1;i<=blo[n];i++){ ll now=0; for(int j=lower_bound(blo+1,blo+n+1,i)-blo;j<=n;j++){ cnt[i][a[j]]++,now=max(now,(ll)cnt[i][a[j]]*b[a[j]]),f[i][j]=now; } } int xx,yy; while(m--){ scanf("%d%d",&xx,&yy);ans=f[blo[xx]+1][yy]; int tmp=lower_bound(blo+1,blo+n+1,blo[yy])-blo;top=0; for(int i=tmp;i<=yy;i++) num[a[i]]++,sta[++top]=a[i]; tmp=lower_bound(blo+1,blo+n+1,blo[xx]+1)-blo; for(int i=xx;i<tmp;i++){ num[a[i]]++;sta[++top]=a[i]; ans=max(ans,(ll)((ll)num[a[i]]+cnt[blo[xx]+1][a[i]]-cnt[blo[yy]][a[i]])*b[a[i]]); } for(int i=1;i<=top;i++)num[sta[i]]=0; printf("%lld\n",ans); } return 0; }

例題2:[HNOI2010]彈飛綿羊

Description:

某天,Lostmonkey發明了一種超級彈力裝置,為了在他的綿羊朋友面前顯擺,他邀請小綿羊一起玩個遊戲。遊戲一開始,Lostmonkey在地上沿著一條直線擺上n個裝置,每個裝置設定初始彈力系數ki,當綿羊達到第i個裝置時,它會往後彈ki步,達到第i+ki個裝置,若不存在第i+ki個裝置,則綿羊被彈飛。綿羊想知道當它從第i個裝置起步時,被彈幾次後會被彈飛。為了使得遊戲更有趣,Lostmonkey可以修改某個彈力裝置的彈力系數,任何時候彈力系數均為正整數。

Solution:

(LCT裸題,我們不管這個,因為我不會)

發現,直接枚舉是n^2的。但是,許多的位置出發,可能會走到同一個位置,再跳出去。

類似於記憶化的思想。

假如設f[i]表示,從i開始,跳f[i]步出去。預處理倒序O(n)即可

查詢是O(1)的。

但是一旦修改了,就會整個前面的f都會變。就涼涼了。。

要是修改只改一部分就好了。

即使查詢不是O(1)也沒關系啊。

分塊,就來把這兩個復雜度平均一下,都變成sqrt(n)

維護f[i],g[i]
f[i] 表示跳幾次可以跳出所在塊
g[i] 表示跳出所在塊後到達的位置。
在查詢時,我們O(sqrt(n))的時間進行“整塊”的模擬,可以得到結果。

這樣,我們以塊為單位,出了塊就交給別人去管就可以了。

f,g的巧妙搭配,使得查詢只需要模擬即可!!

而且,修改時,只有塊內收到了影響。暴力倒序修改即可。

Code:

#include<bits/stdc++.h>
using namespace std;
const int N=200000+10;
int n,m;
int BLO,blo[N];
int f[N],g[N];
int a[N]; 
int ans;
struct node{
    int l,r;
}kua[N];
int main()
{
    scanf("%d",&n);BLO=sqrt(n);
    for(int i=1;i<=n;i++) {
    scanf("%d",&a[i]),blo[i]=(i-1)/BLO+1;
    if(!kua[blo[i]].l) kua[blo[i]].l=i;
    kua[blo[i]].r=i;
    }
    for(int i=1;i<=blo[n];i++){
        for(int j=kua[i].r;j>=kua[i].l;j--){
            int to=j;
            while(to<=kua[i].r){
                if(!f[to]) to=to+a[j],f[j]++;
                else f[j]+=f[to],to=g[to];
            }
            g[j]=to;
        }
    }
    scanf("%d",&m);
    int op,x,y;
    while(m--){
        scanf("%d%d",&op,&x);x++;
        if(op==1){
            ans=0;
            int to=x;
            while(to<=n){
                ans+=f[to];
                to=g[to];
            }
            printf("%d\n",ans);
        }
        else{
            scanf("%d",&y);
            a[x]=y;
            for(int i=kua[blo[x]].r;i>=kua[blo[x]].l;i--){
                f[i]=g[i]=0;
                int to=i;
                while(to<=kua[blo[x]].r){
                    if(!f[to]) to=to+a[i],f[i]++;
                    else f[i]+=f[to],to=g[to];
                }
                g[i]=to;
            }
        }
    }
    return 0;
}

分塊——優化的暴力