树上分块 与 莫队算法

前言:

在序列上我们喜欢的暴力美学就是分块了,莫队算法就是其中之一.
那么我们在树上可以分块,去利用莫队算法做个漂亮的暴力吗?
可以!
要不就没有这篇文章惹

树上分块:

我们先学习一下树上如何分块!
[SCOI2005] 王室联邦
这道题目就是一道经典的树上分块
我们记录一个栈,当一个子树的个数>=块大小的时候,新建一个块,把这些点扔进这个块内.

首先,对于整个树dfs,当子树的大小大于 b 时,就将它们分在一块,同时令首都为当前这个点,容易想到:对于根,可能会剩下一些点,于是将这些点分在最后一个块里,容易发下,最后这一堆与那一块之和一定小于3b,正确性得证。

代码:

#include <cstdio>
#include <algorithm>
#include <cstring>
#include <iostream>
const int maxm=1e5+100;
int id[maxm],cap[maxm],num;
int stk[maxm],top;
int n,b;
int head[maxm],to[maxm<<1],net[maxm<<1];
int cnt;
void addedge(int u,int v)
{
    ++cnt;
    to[cnt]=v,net[cnt]=head[u],head[u]=cnt;
}
void dfs(int x,int fa)
{
    int now=top;
    for(int i=head[x];i;i=net[i])
    if(to[i]!=fa)
    {
        dfs(to[i],x);
        if(top-now>=b)
        {
            cap[++num]=x;
            while(top!=now) id[stk[top--]]=num;
        }
    }
    stk[++top]=x;
}
int main()
{
    scanf("%d%d",&n,&b);
    for(int i=1;i<n;i++)
    {
        int u,v;
        scanf("%d%d",&u,&v);
        addedge(u,v),addedge(v,u);
    }
    dfs(1,0);
    while(top) id[stk[top--]]=num; 
    printf("%d\n",num);
    for(int i=1;i<=n;i++)
     printf("%d ",id[i]);
    puts("");
    for(int i=1;i<=num;i++)
     printf("%d ",cap[i]);
    puts("");
    return 0;
}

莫队算法在树上分块的应用:

看一道例题.
SPOJ Count on a tree II
题目大意就是每个点有一个色,每次询问求u->v路径上不同颜色点的个数.

首先先离散化权值,分块,然后莫队统计…

好吧,怎么莫队统计啊

转移部分,可以考虑将X拨动到NX位置,那么NX向上走到LCA,X走到LCA,将路径取反即可(即存在性取反,这样走过两边的路就消掉了,因为X,Y两个节点走过相同的部分是不需要走的,也就是根节点到LCA(X,Y)的部分是不需要走的);

vis[i] 表示i节点向上的边的存在性,所以vis[LCA(x,y)] 的存在性可能为0,但它又是必须取的,所以特判一下即可。

询问排序时,要根据x,y的块来排序,这样可以让我每次拨动节点的时候,保证最多移动根号N个。

然后搞搞搞就行惹

其实离线的询问我们可以直接用Tarjan求一波LCA,肯定会比倍增快惹,然而我懒。。。
代码:

#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
const int maxm=110000; 
int block_big,block_num,pos[maxm],dfn[maxm],tot;
int hash[maxm],val[maxm],f[maxm],vis[maxm];
int fa[maxm][18],deep[maxm];
int head[maxm],to[maxm<<1],net[maxm<<1],cnt;
int stk[maxm],top,ans;
int Ans[maxm];
int n,m;
struct node{
    int u,v,id;
}q[maxm];
inline void addedge(int u,int v)
{
    cnt++;
    to[cnt]=v,net[cnt]=head[u],head[u]=cnt;
}
void dfs(int x,int fax,int dep)
{
    dfn[x]=++tot,fa[x][0]=fax,deep[x]=dep;
    int now=top;
    for(int i=1;i<=17;i++)
     fa[x][i]=fa[fa[x][i-1]][i-1];
    for(int i=head[x];i;i=net[i])
    if(to[i]!=fax)
    {
        dfs(to[i],x,dep+1);
        if(top-now>=block_big)
        {
            block_num++;
            while(top!=now) pos[stk[top--]]=block_num;
        }
    }
    stk[++top]=x;
}
inline int LCA(int u,int v)
{
    if(deep[u]<deep[v]) std::swap(u,v);
    int k=(deep[u]-deep[v]);
    for(int i=17;~i;i--)
    if((1<<i)&k) u=fa[u][i];
    if(u==v) return u;
    for(int i=17;~i;i--)
    if(fa[u][i]!=fa[v][i])
     u=fa[u][i],v=fa[v][i];
    return fa[u][0];
}
inline void Xor(int x)
{
    if(vis[x]) ans-=((--f[val[x]])==0);
    else ans+=((++f[val[x]])==1);
    vis[x]^=1;
}
inline void update(int u,int v)
{
    if(deep[u]<deep[v]) std::swap(u,v);
    while(deep[u]>deep[v]) Xor(u),u=fa[u][0];
    while(u!=v) Xor(u),Xor(v),u=fa[u][0],v=fa[v][0];
}
inline bool comp(node a,node b)
{
    return pos[a.u]==pos[b.u]?dfn[a.v]<dfn[b.v]:pos[a.u]<pos[b.u];
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
     scanf("%d",&val[i]),hash[i]=val[i];
    std::sort(hash+1,hash+n+1);
    int t=std::unique(hash+1,hash+n+1)-hash-1;
    for(int i=1;i<=n;i++)
     val[i]=std::lower_bound(hash+1,hash+t+1,val[i])-hash-1;
    for(int i=1,u,v;i<n;i++)
    {
        scanf("%d%d",&u,&v);
        addedge(u,v),addedge(v,u);
    }
    block_big=std::sqrt(n);
    dfs(1,0,0);
    block_num++;
    while(top) pos[stk[top--]]=block_num; 
    /*for(int i=1;i<=n;i++)
     printf("%d ",pos[i]);
    puts("");*/ 
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d",&q[i].u,&q[i].v),q[i].id=i;
        if(pos[q[i].u]>pos[q[i].v]) std::swap(q[i].u,q[i].v);
    }

    std::sort(q+1,q+m+1,comp);
    int nowu=1,nowv=1;
    for(int i=1;i<=m;i++)
    {
        if(nowu!=q[i].u) update(nowu,q[i].u);
        if(nowv!=q[i].v) update(nowv,q[i].v);
        Ans[q[i].id]=ans+((f[val[LCA(q[i].u,q[i].v)]])==0);
        nowu=q[i].u,nowv=q[i].v;
        //printf("%d\n",LCA(nowu,nowv));
    }
    for(int i=1;i<=m;i++)
     printf("%d\n",Ans[i]);
    return 0;
}

习题集锦:

集锦个毛啊,好像没有多少题

[WC2013]糖果公园

这个需要我们搞一下修改
在询问的结构体里新增一个时间序,表示本询问处于第几次修改,然后当前与上次时间判断一下,暴力修改就好惹,转移挺简单的
卡常数哇,所有的点答案都爆int惹(有毒
写快读,貌似块的大小设置为n^(2/3)为好,其实sqrt(n)卡一卡也可以过
代码:

#include <cstdio>
#include <algorithm>
#include <iostream>
#include <cmath>
#define ll long long
const int maxm=410000; 
int head[maxm],to[maxm<<1],net[maxm<<1],cnt;
int dfn[maxm],vis[maxm],sum[maxm],deep[maxm],fa[maxm][18],c[maxm],tot;
int stk[maxm],top;
int pos[maxm];
ll w[maxm],v[maxm];
ll ans,Ans[maxm];
int n,m,Q;
int block_big,block_num;
struct node{
    int u,v,timx,id;
}q[maxm];
struct Node{
    int c,id;
}opt[maxm];
inline int comp(node x,node y)
{
    if(pos[x.u]!=pos[y.u]) return pos[x.u]<pos[y.u];
    if(pos[x.v]!=pos[y.v]) return pos[x.v]<pos[y.v];
    return x.timx<y.timx;
}
inline void addedge(int u,int v)
{
    cnt++;
    to[cnt]=v,net[cnt]=head[u],head[u]=cnt;
}
void dfs(int x,int fax,int dep)
{
    dfn[x]=++tot;
    fa[x][0]=fax,deep[x]=dep;
    int now=top;
    for(int i=1;i<=17;i++)
     fa[x][i]=fa[fa[x][i-1]][i-1];
    for(int i=head[x];i;i=net[i])
    if(to[i]!=fax)
    {
        dfs(to[i],x,dep+1);
        if(top-now>=block_big)
        {
            block_num++;
            while(top!=now) pos[stk[top--]]=block_num;
        }
    }
    stk[++top]=x;
}
inline int read1()
{
    int x=0,w=1;
    char ch=0;
    while(ch<'0'||ch>'9')
    {
        if(ch=='-') w=-1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9')
    {
        x=(x<<3)+(x<<1)+ch-'0';
        ch=getchar();
    }
    return x*w;
}
inline ll read2()
{
    ll x=0,w=1;
    char ch=0;
    while(ch<'0'||ch>'9')
    {
        if(ch=='-') w=-1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9')
    {
        x=(x<<3)+(x<<1)+ch-'0';
        ch=getchar();
    }
    return x*w;
}
inline int LCA(int u,int v)
{
    if(deep[u]<deep[v]) std::swap(u,v);
    int k=(deep[u]-deep[v]);
    for(int i=17;~i;i--) 
    if((1<<i)&k) u=fa[u][i];
    if(u==v) return u;
    for(int i=17;~i;i--)
    if(fa[u][i]!=fa[v][i])
    u=fa[u][i],v=fa[v][i];
    return fa[u][0];
}
inline void Xor(int x)
{
    if(vis[x])
    {
        sum[c[x]]--;
        ans-=w[sum[c[x]]+1]*v[c[x]];
    }
    else
    {
        sum[c[x]]++;
        ans+=w[sum[c[x]]]*v[c[x]];
    }
    vis[x]^=1;
}
inline void change(int t)
{
    int x=opt[t].id;
    if(vis[x])
    {
        ans-=v[c[x]]*w[sum[c[x]]--];
        ans+=v[opt[t].c]*w[++sum[opt[t].c]];
    }   
    std::swap(opt[t].c,c[x]);
}
inline void modify(int t1,int t2)
{
    for(int i=t1+1;i<=t2;i++) change(i);
    for(int i=t1;i>=t2+1;i--) change(i);
}
inline void update(int u,int v)
{
    if(deep[u]<deep[v]) std::swap(u,v);
    while(deep[u]>deep[v]) Xor(u),u=fa[u][0];
    while(u!=v) Xor(u),Xor(v),u=fa[u][0],v=fa[v][0];
}
int main()
{
    //freopen("park.in","r",stdin);
    //freopen("park.out","w",stdout);
    n=read1(),m=read1(),Q=read1();
    for(int i=1;i<=m;i++)
     v[i]=read2();
    for(int i=1;i<=n;i++)
     w[i]=read2();
    for(int i=1;i<n;i++)
    {
        int u=read1(),v=read1();
        addedge(u,v),addedge(v,u);
    }
    for(int i=1;i<=n;i++)
     c[i]=read1();
    block_big=(int)std::sqrt(n);
    dfs(1,0,0);
    block_num++;
    while(top) pos[stk[top--]]=block_num;
    int cnt1,cnt2;
    cnt1=cnt2=0;
    for(int i=1;i<=Q;i++)
    {
        int type=read1(),u=read1(),v=read1();
        if(!type) opt[++cnt1]=(Node){v,u}; 
        else q[++cnt2]=(node){u,v,cnt1,cnt2};
    }
    for(int i=1;i<=cnt2;i++)
     if(pos[q[i].u]>pos[q[i].v]) std::swap(q[i].u,q[i].v);
    std::sort(q+1,q+cnt2+1,comp);
    for(int i=1;i<=cnt2;i++)
    {
        update(q[i-1].u,q[i].u);
        update(q[i-1].v,q[i].v);
        modify(q[i-1].timx,q[i].timx);
        int lca=LCA(q[i].u,q[i].v);
        Xor(lca),Ans[q[i].id]=ans,Xor(lca);
    }
    for(int i=1;i<=cnt2;i++)
    printf("%lld\n",Ans[i]);
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值