分块,莫队算法总结

分块算法总结

  分块,就是一种暴力算法,不过复杂度优于暴力,是基于足够的预处理和合理可行的维护操作进行优化时间,

 在预处理+维护的耗时上与暴力处理的耗时上找到一种平衡,于是出了这个优美的算法

  标志:查询某一区间内元素种类数,查询某一区间大于等于某一元素的数的个数(即排名

 模板:LuoguP2801 教主的魔法

  为了查询大于等于C的个数,可以排序,用区间长度-C的排名就是 答案数。

  所以可以动态维护一个块内有序的数组

 

#include
#define re register
#define inc(i,j,k) for(re int i=j;i<=k;++i)
#define il inline 
using namespace std;
const int maxn=1000010;
vector <int> ve[1010];
int v[maxn],bl[maxn],atag[maxn];
int n,m,base;
il void reset(int x) //重新维护一个块内元素的顺序
{
    ve[x].clear();
    inc(i,(x-1)*base+1,min(x*base,n)) ve[x].push_back(v[i]);//别忘了容易溢出n的数组大小呀
    sort(ve[x].begin(),ve[x].end());
}
il void add(int a,int b,int c)//区间加
{
    inc(i,a,min(bl[a]*base,b)) v[i]+=c;//暴力处理前半部分
    reset(bl[a]);
    if(bl[a]!=bl[b]) {inc(i,(bl[b]-1)*base+1,b) v[i]+=c; reset(bl[b]);}//暴力处理后面部分
    inc(i,bl[a]+1,bl[b]-1) atag[i]+=c;
}
il int query(int a,int b,int c)//区间查询
{
    int ans=0;
    inc(i,a,min(bl[a]*base,b)) if(v[i]+atag[bl[a]]; //暴力处理前部分
    if(bl[a]!=bl[b])
    {
        inc(i,(bl[b]-1)*base+1,b) if(v[i]+atag[bl[b]];//暴力处理后面部分
    }
    inc(i,bl[a]+1,bl[b]-1)//对于块来说,直接减去lazy_tag的影响
    {
        int x=c-atag[i];
        ans+=lower_bound(ve[i].begin(),ve[i].end(),x)-ve[i].begin();//查询第一个大于等于x的数的位置
    }
    return ans;
}
int main()
{
    scanf("%d %d",&n,&m);
    base=sqrt(n);
    inc(i,1,n) scanf("%d",&v[i]),bl[i]=(i-1)/base+1,ve[bl[i]].push_back(v[i]);
    inc(i,1,bl[n]) sort(ve[i].begin(),ve[i].end());
    char s;
    re int x,y,z;
    inc(i,1,m)
    {
        s=getchar();
        while(s!='A'&&s!='M') s=getchar();
        if(s=='M')
        {
            scanf("%d %d %d",&x,&y,&z);
            add(x,y,z);
        }
        else 
        {
            scanf("%d %d %d",&x,&y,&z);
            printf("%d\n",y-x+1-query(x,y,z));
        }
    }
}

 

其他例题先咕着吧

 

然后...

莫队

莫队其实也是基于分块的算法,主要是一种思想吧我认为,基于对询问问题的离线处理按一定顺序去处理问题

然后保证了暴力的复杂度

具体实现是要按左区间的块排序,快相同的按右端点排序,保证同一个块内复杂度O(n),共有sqrt(n)块

所以总复杂度 O(n*sqrt(n))

主要用于查询元素的种类数

模板:LuoguP2709 小B的询问

#include
#define ll long long
#define re register
using namespace std;
#define maxn 50005
struct query
{
    int l,r,id,pos;
//    friend bool operator < (query xx,query yy)
//    {    if(xx.pos==yy.pos) return xx.l
    bool operator <(const query &x) const {if(pos==x.pos)return r<x.r;
   else return pos<x.pos;} 
}a[maxn];
int b[maxn],n,m,K;ll cnt[maxn],Ans[maxn];
inline int read(){
    int f=1,x=0;char ch;
    do{ch=getchar();if(ch=='-')f=-1;}while(ch<'0'||ch>'9');
    do{x=x*10+ch-'0';ch=getchar();}while(ch>='0'&&ch<='9');
    return x*f;
}
int main()
{
//    freopen("testdata.in.txt","r",stdin);
//    freopen("4321.txt","w",stdout);
    n=read(); m=read(); K=read();
    int siz=(int) sqrt(n);
    for(re int i=1;i<=n;++i) b[i]=read();
    for(re int i=1;i<=m;++i)
    {
        a[i].l=read(); a[i].r=read(); a[i].id=i;
        a[i].pos=(a[i].l-1)/siz+1;
    }
    sort(a+1,a+m+1);
    int l=1,r=0; long long ans=0;
    for(re int i=1;i<=m;++i)
    {
        while(l>a[i].l) { l--; cnt[b[l]]++; ans+= 2*cnt[b[l]]-1 ;} //外拓的时候,先加减再统计
        while(r2*cnt[b[r]]-1 ;}
        while(r>a[i].r) {cnt[b[r]]--; ans-= 2*cnt[b[r]]+1 ; r--; } //回溯的时候,先统计再加减
        while(l2*cnt[b[l]]+1 ; l++; }
        Ans[a[i].id]=ans;
    }
    for(re int i=1;i<=m;++i) printf("%lld\n",Ans[i]);
}

其次就是对于莫队的修改操作了,带修莫队与原版的差异是多了个时间轴

把修改操作看作删除旧的+增加新的,这种思想很多时候都用的上

也是裸暴力,只不过多关注时间轴上修改都干了什么,这样查询的时候只考虑当前时间之前的操作就好了

分块大小为 (n*t)1/3  复杂度O((n4*t)1/3)   ->不会证

模板题 LuoguP1903 数颜色

//窝洛谷突然看不了自己提交的代码了。。。先复制第一篇题解的把,,等哪天我想起来再更(逃

#include
#include
#include
using namespace std;
#define fo(a,b,c) for(int a=b;a<=c;a++)
#define go(a,b,c) for(int a=b;a>=c;a--)
int read(){
    int a=0,f=0;char c=getchar();
    for(;c<'0'||c>'9';c=getchar())if(c=='-')f=1;
    for(;c>='0'&&c<='9';c=getchar())a=a*10+c-'0';
    return f?-a:a;
}
const int N=10001;
int a[N],p[1000001],ans[N],divi;
struct nod{int pla,pre,suc;}cg[N];
struct node{int l,r,t,bel;}ls[N];
int cmp(node a,node b){
    if(a.l/divi!=b.l/divi)return a.l/dividivi;
    if(a.r/divi!=b.r/divi)return a.r/dividivi;
    return a.t<b.t; 
}
int main(){
    int n=read(),m=read(),ln=0,tn=0,l=1,r=0,t=0,num=0;
    fo(i,1,n)a[i]=read();
    fo(i,1,m){
        scanf("\n");
        if(getchar()=='R'){//如果读入修改则记录修改的地点,修改前的数字和修改后的数字
            ++tn;cg[tn].pla=read(),cg[tn].suc=read();
            cg[tn].pre=a[cg[tn].pla];
            a[cg[tn].pla]=cg[tn].suc;
        } 
        else ls[++ln]=(node){read(),read(),tn,ln};
    }
    divi=ceil(exp((log(n)+log(tn))/3));//分块大小
    go(i,tn,1)a[cg[i].pla]=cg[i].pre;
    sort(ls+1,ls+ln+1,cmp);
    fo(i,1,m){
        while(ls[i].l;
        while(ls[i].l>l)num-=!--p[a[l++]];//l移动
        while(ls[i].r>r)num+=!p[a[++r]]++;
        while(ls[i].r//r移动
        while(ls[i].t<t){
            int pla=cg[t].pla;
            if(l<=pla&&pla<=r)num-=!--p[a[pla]];
            a[pla]=cg[t--].pre;
            if(l<=pla&&pla<=r)num+=!p[a[pla]]++;
        };
        while(ls[i].t>t){
            int pla=cg[++t].pla;
            if(l<=pla&&pla<=r)num-=!--p[a[pla]];
            a[pla]=cg[t].suc;
            if(l<=pla&&pla<=r)num+=!p[a[pla]]++;
        };//t移动
        ans[ls[i].bel]=num;
    }
    fo(i,1,ln)printf("%d\n",ans[i]); 
    return 0;
}

二次离线莫队

updataing......

 

你可能感兴趣的:(分块,莫队算法总结)