分块——优化的暴力
Posted miracevin
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了分块——优化的暴力相关的知识,希望对你有一定的参考价值。
对于很多的题目,我们都可以找到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 ",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 ",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; }
以上是关于分块——优化的暴力的主要内容,如果未能解决你的问题,请参考以下文章