[学习笔记]莫队

莫队

前言

完了我发现什么都不会啊w(゚Д゚)w

参考博文

【莫队算法】-大米饼
上面这篇博文写的很好 尤其是复杂度的证明这一块
很精辟

正文

  • 莫队是啥
    暴力的优化
    通过排序来减少枚举的复杂度
  • 什么决定了莫队的复杂度
    几个指针的跳动
  • 莫队可以解决什么问题
    • 需要离线解决问题
    • 用于区间问题 且 能以极少的时间向左右两边转换(一般为\(O(1)\)
    • 对于修改与不修改没什么要求

但莫队的思想美妙,代码优美,你值得拥有。莫队的排序思想也为众多离线处理的题目提供了完整的思路。

入门题:

两个指针的跳动bzoj2038|2009国家集训队|小Z的袜子

给你一个颜色序列 每次询问\([l,r]\)区间里随便选两个是相同颜色的概率

\(num[i]\)表示颜色为\(i\)的袜子数量区间长度为\(len\) 显然答案为\(\frac{\sum_{i=1}^{n}num[i]^2-num[i]}{len*len-len}\)

如果你知道\([l,r]\)的答案相信你一定能\(O(1)\)求出\([l,r+1]\)的答案

两个指针的跳动 最好的块分的大小为\(\sqrt{n}\) 时间复杂度为\(n^{\frac{3}{2}}\) 下面给出证明TAT

设块的大小为\(sz\) 对询问排序的时候 如果\(l\)在同一个块里 \(r\)排序 不在一个块里按\(l\)排序
那么对于一个块

  • \(r\)指针的跳动次数最坏为\(n\) 所以总的跳动次数为\(n*\frac{n}{unit}\)
  • \(l\)指针 注意\(l\)在一个块中是无序的 所以在一个块中一次\(l\)的最大跨度为\(unit\) 又一个块中最多跳\(m\)次 所以\(l\)的复杂度为\(m*unit\)
    \(m\)\(n\)同级 所以总复杂度为\(n*unit+\frac{n*n}{unit}\)
    可以由基本不等式得 复杂度的最小值为\(n\sqrt{n}\) 取到最小值时\(unit\)\(\sqrt{n}\)
#include<bits/stdc++.h>
#define fr(i,x,y) for(int i=x;i<=y;++i)
#define rf(i,x,y) for(int i=x;i>=y;--i)
#define LL long long
using namespace std;
const int N=5e4+10;
struct data{
    int l,r,id;
}a[N];
int A[N],B[N];
LL fz[N],fm[N],nm[N];

bool cmp(data xx,data yy){
    return B[xx.l]==B[yy.l]?xx.r<yy.r:xx.l<yy.l;
}

LL gcd(LL x,LL y){
    return y?gcd(y,x%y):x;
}

LL pf(LL x){
    return x*x;
}

int main(){
    int n,m;scanf("%d%d",&n,&m);
    int sz=sqrt(n);
    fr(i,1,n) scanf("%d",&A[i]),B[i]=i/sz+1;
    fr(i,1,m){
        a[i].id=i;
        scanf("%d%d",&a[i].l,&a[i].r);
    }
    sort(a+1,a+1+m,cmp);
    int l=1,r=0;
    LL ff=0;
    fr(i,1,m){
        while(l<a[i].l) ff-=pf(nm[A[l]]),ff+=pf(--nm[A[l]]),l++;
        while(l>a[i].l) l--,ff-=pf(nm[A[l]]),ff+=pf(++nm[A[l]]);
        while(r<a[i].r) r++,ff-=pf(nm[A[r]]),ff+=pf(++nm[A[r]]);
        while(r>a[i].r) ff-=pf(nm[A[r]]),ff+=pf(--nm[A[r]]),r--;
        LL pos=a[i].r-a[i].l+1,sum=pos*pos;
        fz[a[i].id]=ff-pos,fm[a[i].id]=sum-pos;
        LL gg=gcd(ff-pos,sum-pos);
        if(a[i].l==a[i].r) fz[a[i].id]=0,fm[a[i].id]=1;
        else fz[a[i].id]/=gg,fm[a[i].id]/=gg;
    }
    fr(i,1,m) printf("%lld/%lld\n",fz[i],fm[i]);
    return 0;
}

mdT了好久啊TAT!!!

三个指针的跳动bzoj2120数颜色

给你一个颜色序列 操作有两种 1. 修改x上的颜色为\(y\) 2.询问\([l,r]\)区间里一共有多少个不同的颜色

三个指针的莫队 考虑其分成多少大的块(块的最佳大小为\(n^\frac{2}{3}\) 时间复杂度为\(n^\frac{5}{3}\)) 下面给出证明:
排序的时候

bool cmp(Query xx,Query yy){
    if(B[xx.l]==B[yy.l]){
        if(B[xx.r]==B[yy.r]) return xx.T<yy.T;
        else return xx.r<yy.r;
    } else return xx.l<yy.l;
}

和上面一样分别对\(l,r,T\)进行讨论

  • 对于\(l\) 同上是\(m*unit\)\(n*unit\)
  • 对于\(r\) 同上是\(\frac{n*n}{unit}\)
  • 对于\(T\) 可以观察到只有\(l,r\)在同一个块里的时候才会有序
    • 那么最坏的情况下 一个\(l\)块里有所有的\(r\)
      那么\(T\)的单调块就有\(\frac{n}{unit}*\frac{n}{unit}\)\(T\)的复杂度为\(n*\frac{n*n}{unit*unit}\)

综上总复杂度为\(O(n*unit+\frac{n*n}{unit}+n*\frac{n*n}{unit*unit})\)

是不是该去问数竞大佬这个不等式怎么搞最小值的TAT

#include<bits/stdc++.h>
#define fr(i,x,y) for(int i=x;i<=y;++i)
#define rf(i,x,y) for(int i=x;i>=y;--i)
#define LL long long
using namespace std;
const int N=1e4+10,M=1e6+10;
struct Query{ int id,l,r,T; }q[N];
struct Change{ int x,nw,od; }c[N];
int pre[N],B[N],nm[M],d[N],a[N],ans=0;

bool cmp(Query xx,Query yy){
    if(B[xx.l]==B[yy.l]){
        if(B[xx.r]==B[yy.r]) return xx.T<yy.T;
        else return xx.r<yy.r;
    } else return xx.l<yy.l;
}

void Upd(int x,int flg){
   // printf("GG %d %d\n",x,flg);
    if(nm[x]) ans--;
    nm[x]+=flg;
    if(nm[x]) ans++;
}

void Go(int sit,int x,int y,int l,int r){
  //  printf("gg%d %d %d %d %d\n",sit,x,y,l,r);
    if(sit>=l&&sit<=r) Upd(x,-1),Upd(y,1);
    a[sit]=y;
}

int main(){
    int n,m;scanf("%d%d",&n,&m);
    int sz=pow(n,0.666666);
    fr(i,1,n) scanf("%d",&pre[i]),a[i]=pre[i],B[i]=i/sz+1;
    int T=0,cnt=0;
    fr(i,1,m){
        char ch;cin>>ch;
        int x,y;scanf("%d%d",&x,&y);
        if(ch=='Q') q[++cnt]=(Query){cnt,x,y,T};
        else c[++T]=(Change){x,y,pre[x]},pre[x]=y;
    }
    sort(q+1,q+1+cnt,cmp);
    /*fr(i,1,cnt){
        printf("id=%d l=%d r=%d T=%d\n",q[i].id,q[i].l,q[i].r,q[i].T);
    }*/
    int l=1,r=0;T=0;
    fr(i,1,cnt){
        while(q[i].T>T) T++,Go(c[T].x,c[T].od,c[T].nw,l,r);
        while(q[i].T<T) Go(c[T].x,c[T].nw,c[T].od,l,r),T--;
        while(l<q[i].l) Upd(a[l],-1),l++;
        while(l>q[i].l) l--,Upd(a[l],1);
        while(r<q[i].r) r++,Upd(a[r],1);
        while(r>q[i].r) Upd(a[r],-1),r--;
        d[q[i].id]=ans;
    }
    fr(i,1,cnt) printf("%d\n",d[i]);
    return 0;
}

树上莫队带修改bzoj1429|Haruna’s Breakfast

续了我好久QwQ

树上分块+颜色块

写的太长了TAT

#include<bits/stdc++.h>
#define fr(i,x,y) for(int i=x;i<=y;++i)
#define rf(i,x,y) for(int i=x;i>=y;--i)
#define LL long long
using namespace std;
const int N=5e4+10;
struct Query{ int id,u,v,T; }q[N];
struct Change{ int x,od,nw; }c[N];
int tp=0,Q[N],col=0;
int n,m,sz,gg,bk,cnt=0,res=0;
int B[N],b[N],L[N],R[N],sum[N],nm[N],a[N],pre[N],vis[N],d[N];/*B:tree  b:w*/
int eu[N<<1],to[N<<1],nt[N<<1],mk[N],st[N<<1][20],head[N],dep[N],f[N];

void add(int x,int y){
    to[++cnt]=y,nt[cnt]=head[x],head[x]=cnt;
    to[++cnt]=x,nt[cnt]=head[y],head[y]=cnt;
}

void dfs(int u,int fa){
    f[u]=fa,dep[u]=dep[fa]+1;
    eu[++res]=u;
    int lt=tp;
    for(int i=head[u];i;i=nt[i]){
        if(to[i]==fa) continue;
        dfs(to[i],u);
        if(tp-lt>=sz){
            ++col;
            while(tp!=lt) B[Q[tp--]]=col;
        }
        eu[++res]=u;
    }
    Q[++tp]=u;
}

void Gao(){
    while(tp) B[Q[tp--]]=col;
    memset(mk,-1,sizeof mk);
    fr(i,1,res){
        if(mk[eu[i]]==-1) mk[eu[i]]=i;
        st[i][0]=eu[i];
    }
    fr(i,1,19) fr(j,1,res){
        if(j+(1<<i)+1>=res) continue;
        int pos=st[j][i-1],tmp=st[j+(1<<(i-1))][i-1];
        if(dep[pos]<dep[tmp]) st[j][i]=pos;
        else st[j][i]=tmp;
    }
}

int GetLCA(int l,int r){
    if(l>r) swap(l,r);
    int kk=log(double(r-l+1))/log(2.0);
    int pos=st[l][kk],tmp=st[r-(1<<kk)+1][kk];
    if(dep[pos]<dep[tmp]) return pos;
    else return tmp;
}

bool cmp(Query xx,Query yy){
    if(B[xx.u]==B[yy.u]){
        if(B[xx.v]==B[yy.v]) return xx.T<yy.T;
        else return xx.v<yy.v;
    } else return xx.u<yy.u;
}

void Gai(int sit,int x,int flg){
    if(x>n) return ;
    if(nm[x]) sum[b[x]]--;
    nm[x]+=flg;
    if(nm[x]) sum[b[x]]++;
}

void Upd(int sit,int x,int y){
    if(vis[sit]){
        Gai(sit,x,-1),Gai(sit,y,1);
    }
    a[sit]=y;
}

int Get(){
    fr(i,1,gg){
        if(sum[i]==R[i]-L[i]+1) continue;
        else fr(j,L[i],R[i]) if(!nm[j]) {
            return j;
        }
    }
}

void init(){
    bk=sqrt(n);sz=pow(n,0.45);
    fr(i,0,n) b[i]=i/bk+1;
    gg=n/bk+1;
    fr(i,1,gg) L[i]=(i-1)*bk,R[i]=min(n,i*bk);
}

void fz(int x){
    if(vis[x]) Gai(x,a[x],-1);
    else Gai(x,a[x],1);
    vis[x]^=1;
}

void Go(int x,int y){
    if(dep[x]>dep[y]) swap(x,y);
    while(dep[y]>dep[x]) fz(y),y=f[y];
    while(x!=y) fz(x),fz(y),x=f[x],y=f[y];
}

int main(){
    scanf("%d%d",&n,&m);
    init();
    fr(i,1,n) scanf("%d",&a[i]),pre[i]=a[i];
    for(int i=1,x,y;i<n;++i){
        scanf("%d%d",&x,&y);
        add(x,y);
    }
    dfs(1,0),Gao();
    int T=0,tot=0;
    fr(i,1,m){
        int x,y,z;scanf("%d%d%d",&x,&y,&z);
        if(x) q[++tot]=(Query){tot,y,z,T};
        else c[++T]=(Change){y,pre[y],z},pre[y]=z;
    }
    T=0;
    int nu=1,nv=1;
    sort(q+1,q+1+tot,cmp);
    fr(i,1,tot){
        while(q[i].T>T) T++,Upd(c[T].x,c[T].od,c[T].nw);
        while(q[i].T<T) Upd(c[T].x,c[T].nw,c[T].od),T--;
        if(q[i].v!=nv) Go(q[i].v,nv),nv=q[i].v;
        if(q[i].u!=nu) Go(q[i].u,nu),nu=q[i].u;
        int lca=GetLCA(mk[q[i].u],mk[q[i].v]);
        fz(lca);
        d[q[i].id]=Get();
        fz(lca);
    }
    fr(i,1,tot) printf("%d\n",d[i]);
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/lowbigpei/p/10665695.html