莫队

        莫队是一个优美的暴力

   当我们可以在 O(1) 时间推到 L[i-1], L[i+1] , R[i-1] , R[i+1] 的时候 ,就可以使用莫队来解决这个问题啦

  

  一  普通莫队

  首先分块,将询问的区间按照左端点所处的块来排序,然后按顺序处理每一个询问就好了

  这里有一个小 trick ,排序的时候可以按照块的奇偶性来排序,可以大大的提高效率,虽然我并不知道为什么。

  

  经典例题: 小Z的袜子

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<cmath>
#include<stack>
#include<queue>
using namespace std;
typedef long long ll;

const int maxn = 100010;

int n,m,blo;
int l=1,r=0;
int c[maxn],cnt[maxn],pos[maxn];
ll res=0;

struct Q{
    int l,r,id;
}q[maxn];
struct Ans{
    ll a,b;
}ans[maxn];

bool cmp(Q a,Q b){
    if(pos[a.l]==pos[b.l]) return a.r<b.r;
    return a.l<b.l;
}

void add(int i){
    res-=1ll*cnt[c[i]]*cnt[c[i]];
    ++cnt[c[i]];
    res+=1ll*cnt[c[i]]*cnt[c[i]];
}

void del(int i){
    res-=1ll*cnt[c[i]]*cnt[c[i]];
    --cnt[c[i]];
    res+=1ll*cnt[c[i]]*cnt[c[i]];
}

ll gcd(ll a,ll b){
    if(a<b) swap(a,b); 
    return b==0?a:gcd(b,a%b);
}

ll read(){ ll s=0,f=1; char ch=getchar(); while(ch<'0' || ch>'9'){ if(ch=='-') f=-1; ch=getchar(); } while(ch>='0' && ch<='9'){ s=s*10+ch-'0'; ch=getchar(); } return s*f; }

int main(){
    n=read(),m=read();
    blo=sqrt(n);
    for(int i=1;i<=n;i++) c[i]=read(),pos[i]=(i-1)/blo+1;
    for(int i=1;i<=m;i++){
        q[i].l=read(),q[i].r=read();
        q[i].id=i;
    }
    
    sort(q+1,q+1+m,cmp); 
    
    for(int i=1;i<=m;i++){
        while(l<q[i].l){ del(l); l++; }
        while(l>q[i].l){ add(l-1); l--; }
        while(r<q[i].r){ add(r+1); r++; }
        while(r>q[i].r){ del(r); r--; }
        
        if(q[i].l==q[i].r){
            ans[q[i].id]=(Ans){0,1};
            continue;
        }
        ans[q[i].id]=(Ans){res-(r-l+1),1ll*(r-l+1)*(r-l)};
    }
    
    for(int i=1;i<=m;i++){
        ll g=gcd(ans[i].a,ans[i].b);
        printf("%lld/%lld\n",ans[i].a/g,ans[i].b/g); 
    }
    
    return 0;
}
View Code

  二  带修改莫队

  带修改莫队是在普通莫队的基础上增加了修改操作

  为保证正确性,在询问之前,所有在该次询问时间点之前的修改操作都要修改完 ,之后的修改操作都不能修改

       所以带修莫队要比普通莫队的询问多维护一个时间节点

  那么怎么维护答案呢?

  每次修改答案的时候,如果当前修改的时间点(head)大于询问时间点(q[i].t)那么就将修改还原, 否则继续修改

  剩下的就与普通莫队一样了

  经典例题: 数颜色

  

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<cmath>
#include<stack>
#include<queue>
using namespace std;
typedef long long ll;

const int maxn = 100010;

int n,m,blo,cnt1,cnt2;
int c[maxn],cnt[1000010],pos[maxn],last[maxn],ans[maxn];
int L=1,R=0,res,head=0;

struct C{
    int x,y,last;
}md[maxn];
struct Q{
    int l,r,id,t;
}q[maxn];
bool cmp(Q a,Q b){
    if(pos[a.l]==pos[b.l]){
        if(pos[a.r]==pos[b.r]) return a.t<b.t;
        return pos[a.r]<pos[b.r]; 
    }
    return pos[a.l]<pos[b.l];
}

void change(int x,int col){
    if(L<=x&&x<=R){
        cnt[c[x]]--; if(cnt[c[x]]==0) res--;
        c[x]=col;
        if(cnt[c[x]]==0) res++; cnt[c[x]]++;
    }else
        c[x]=col;
}

void add(int x){
    if(cnt[c[x]]==0) ++res;
    ++cnt[c[x]];
}

void del(int x){
    --cnt[c[x]];
    if(cnt[c[x]]==0) --res;
}
inline int getb(int x){ return (x-1)/blo+1; }

ll read(){ ll s=0,f=1; char ch=getchar(); while(ch<'0' || ch>'9'){ if(ch=='-') f=-1; ch=getchar(); } while(ch>='0' && ch<='9'){ s=s*10+ch-'0'; ch=getchar(); } return s*f; }

int main(){
    n=read(),m=read(); blo=sqrt(n)*10;
    for(int i=1;i<=n;i++) c[i]=read(),pos[i]=(i-1)/blo+1,last[i]=c[i];
    
    char op[10];
    for(int i=1;i<=m;i++){
        scanf("%s",op);
        if(op[0]=='R'){
            md[++cnt1].x=read(); md[cnt1].y=read();
            md[cnt1].last=last[md[cnt1].x];
            last[md[cnt1].x]=md[cnt1].y;
        }else{
            q[++cnt2].l=read(),q[cnt2].r=read(),q[cnt2].id=cnt2;
            q[cnt2].t=cnt1;
        }
    }
    
    sort(q+1,q+1+cnt2,cmp);
    
    for(int i=1;i<=cnt2;i++){
        while(head>q[i].t){
            change(md[head].x,md[head].last);
            head--;
        }
        while(head<q[i].t){
            head++;
            change(md[head].x,md[head].y);
        }
        while(L<q[i].l){ del(L); L++; }
        while(L>q[i].l){ add(L-1); L--; }
        while(R<q[i].r){ add(R+1); R++; }
        while(R>q[i].r){ del(R); R--; }
        ans[q[i].id]=res;
    } 
    
    for(int i=1;i<=cnt2;i++) printf("%d\n",ans[i]);
    
    return 0;
}
View Code

 

  三  树上莫队

   树上的莫队当然还是分块啦

  怎么在树上分块呢?  参考一下王室联邦的树分块

  分块代码:

·  

int dfs(int u,int par){ // 树分块 
    int co=1;
    sta[++top]=u;
    dfn[u]=++tot;
    fa[u][0]=par;
    dep[u]=dep[par]+1;
    
    for(int i=1;i<=20;i++){
        fa[u][i]=fa[fa[u][i-1]][i-1];
    }
    
    for(int i=h[u];i!=-1;i=e[i].next){
        int v=e[i].to;
        if(v==par) continue;
        co+=dfs(v,u);
        if(co>=blo){
            ++cnt;
            while(co){
                kuai[sta[top--]]=cnt;
                co--;
            }
            co=0;
        }
    }
    return co;
}

  

  然后就是将树上问题转变成序列问题 -- dfs 序

  接下来的套路与普通的带修改莫队就是一样的了

  每次对答案的维护直接暴力就好  

void change(int x,int y){  // 暴力更新答案 
    while(x!=y){
        if(dep[x]>dep[y]) update(x),x=fa[x][0];
        else update(y),y=fa[y][0];
    }
}

  

  要注意处理 lca 的答案

  做完了~

  

// luogu-judger-enable-o2
// 糖果公园
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<cmath>
using namespace std;
typedef long long ll;

const int maxn = 100010;

int n,m,q,blo,tot,cnt1,cnt2;
int v[maxn],w[maxn],c[maxn],last[maxn],kuai[maxn],vis[maxn],ci[maxn];
int head=0;
ll res=0,ans[maxn];

int h[maxn],size;
struct E{
    int to,next;
}e[maxn<<1];
void add(int u,int v){
    e[++size].to=v;
    e[size].next=h[u];
    h[u]=size;
}

struct C{
    int x,y,last;
}rc[maxn];
struct Q{
    int x,y,bx,by,tim,id;
}rq[maxn];

bool cmp(Q a,Q b){
    if(a.bx==b.bx){
        if(a.by==b.by) return a.tim<b.tim;
        return a.by<b.by;
    }
    return a.bx<b.bx;
}

int sta[maxn],top;
int dep[maxn],fa[maxn][25],dfn[maxn],cnt; // cnt 块的数量  dfn 括号序列 

int dfs(int u,int par){ // 树分块 
    int co=1;
    sta[++top]=u;
    dfn[u]=++tot;
    fa[u][0]=par;
    dep[u]=dep[par]+1;
    
    for(int i=1;i<=20;i++){
        fa[u][i]=fa[fa[u][i-1]][i-1];
    }
    
    for(int i=h[u];i!=-1;i=e[i].next){
        int v=e[i].to;
        if(v==par) continue;
        co+=dfs(v,u);
        if(co>=blo){
            ++cnt;
            while(co){
                kuai[sta[top--]]=cnt;
                co--;
            }
            co=0;
        }
    }
    
    return co;
}

int LCA(int u,int v){  // 树上莫队要注意对 LCA 的处理 
    if(dep[u]<dep[v]) swap(u,v);
    for(int j=20;j>=0;j--){
        if(dep[fa[u][j]]>=dep[v]) u=fa[u][j];
    }  if(u==v) return u;
    for(int j=20;j>=0;j--){
        if(fa[u][j]!=fa[v][j]){
            u=fa[u][j],v=fa[v][j];
        }
    }return fa[u][0];
} 

void update(int x){  // 从答案中消除这个点 
    if(vis[x]){ 
        vis[x]=0; // 标为没有计入答案 
        res-=1ll*w[ci[c[x]]]*v[c[x]];
        --ci[c[x]];
    }else{
        vis[x]=1;
        ++ci[c[x]];
        res+=1ll*w[ci[c[x]]]*v[c[x]];
    }
}

void change(int x,int y){  // 暴力更新答案 
    while(x!=y){
        if(dep[x]>dep[y]) update(x),x=fa[x][0];
        else update(y),y=fa[y][0];
    }
}

void modify(int x,int C){
    if(!vis[x]) c[x]=C;
    else{
        update(x);
        c[x]=C;
        update(x);
    } 
}

int main(){
    memset(h,-1,sizeof(h));
    scanf("%d%d%d",&n,&m,&q);
    blo=(int)pow(n,0.60);
//    printf("%d\n",blo);
    for(int i=1;i<=m;i++) scanf("%d",&v[i]);
    for(int i=1;i<=n;i++) scanf("%d",&w[i]);
    int u,v;
    for(int i=1;i<n;i++){
        scanf("%d%d",&u,&v);
        add(u,v),add(v,u);
    }
    for(int i=1;i<=n;i++){
        scanf("%d",&c[i]);
        last[i]=c[i];
    }
    dfs(1,0);
    
    int op,x,y;
    for(int i=1;i<=q;i++){
        scanf("%d%d%d",&op,&x,&y);
        if(op==0){
            rc[++cnt1].x=x,rc[cnt1].y=y,rc[cnt1].last=last[x],last[x]=y;
        }else{
            if(dfn[x]>dfn[y]) swap(x,y); // 括号序列中的顺序 
            rq[++cnt2].x=x,rq[cnt2].y=y,rq[cnt2].tim=cnt1,rq[cnt2].id=cnt2;
            rq[cnt2].bx=kuai[x],rq[cnt2].by=kuai[y];
        }
    }
    
    sort(rq+1,rq+1+cnt2,cmp);
    
    int lca=LCA(rq[1].x,rq[1].y);
    while(head<rq[1].tim) ++head,modify(rc[head].x,rc[head].y);
    change(rq[1].x,rq[1].y);
    update(lca);
    ans[rq[1].id]=res;
    update(lca);
    for(int i=2;i<=cnt2;i++){
        while(head>rq[i].tim) modify(rc[head].x,rc[head].last),--head;
        while(head<rq[i].tim) ++head,modify(rc[head].x,rc[head].y);
        change(rq[i-1].x,rq[i].x);
        change(rq[i-1].y,rq[i].y);
        lca=LCA(rq[i].x,rq[i].y);
        update(lca);
        ans[rq[i].id]=res;
        update(lca); 
    }
    
    for(int i=1;i<=cnt2;i++){
        printf("%lld\n",ans[i]);
    }printf("\n");
    
    return 0;
}
View Code

 

posted @ 2018-12-15 16:05  Tartarus_li  阅读(392)  评论(0编辑  收藏  举报