树上莫队

树上莫队

引入

  • 树上莫队看名字就知道,其实是把莫队搬到了树上。一般来说,这种问题有几个特征:

    • 询问可以离线
    • 答案并不好用树形DP或者数据结构维护,往往只能暴力跑到所有的点求。
    • 我们拿一道例题:SP10707 COT2
  • 这样的话,我们很容易就想到需要用莫队。可是莫队算法是基于一个序列的,我们怎么在树上跑莫队呢?
  • 把树整成一个序列不就好了。一般来说,有dfs序和欧拉序两种情况。
  • 什么?你问我什么是欧拉序?

欧拉序

  • 操作:当访问到一个点的时候,把它加进序列。当离开这个点的时候,把它加进序列。
  • 举个栗子:

    这棵树的欧拉序就是:1,2,3,6,6,4,4,5,5,3,7,7,2,1

树上莫队

  • 有了这个序列有什么用?
  • 就拿刚才那道题来说。题目是给你m组询问,每个询问给定一个x,y,问在x~y的这条路径上,所有经过的点的权值中,本质不同的数有多少个。
  • 那么假如我们给了一个x,y,该怎么办?
  • 分类讨论:
    • 如果lca等于x,那么路径上的点肯定在x的子树里,并且y的子树里的点一定没有用。所以我们要找的点在s【x】~s【y】之间(s【x】表示x节点在序列里第一次出现的位置,t【x】即第二次出现的位置)。
    • 如果lca不等于x,那么路径上的点肯定不在x和y的子树里。所以我们要找的点在t【x】~s【y】之间。当然别忘了特判lca。
    • 但是还有一个问题就是,这个区间序列内仍会有无用点,不过无用点一定出现了两次,所以差分一下就好了。
  • 至此,我们已经解决了问题。

Coding

#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=a;i<=b;++i)
#define per(i,a,b) for(int i=a;i>=b;--i)
using namespace std;
const int N=4e5+10;
struct node{
    int l,r,id,add,ans;
}q[N];
int n,m,tot,num,blo,ans,cnt[N],dep[N],v[N],s[N*2],t[N*2],pot[N*2],fa[N][25],ver[N],Next[N],lin[N],a[N],b[N];
void discrete(){
    sort(b+1,b+n+1);
    for(int i=1;i<=n;++i) a[i]=lower_bound(b+1,b+n+1,a[i])-b;
}
void dfs(int x,int f){
    fa[x][0]=f,s[x]=++num,pot[num]=x;
    rep(i,1,20)if((1<<i)<=dep[x])fa[x][i]=fa[fa[x][i-1]][i-1];
    for(int i=lin[x];i;i=Next[i]){
        int y=ver[i];
        if(y==f)continue;
        dep[y]=dep[x]+1;
        dfs(y,x);
    }t[x]=++num,pot[num]=x;
}
int lca(int x,int y){
    if(dep[x]<dep[y])swap(x,y);
    per(i,20,0)if((1<<i)<=dep[x]-dep[y])x=fa[x][i];
    if(x==y)return x;
    per(i,20,0)if(fa[x][i]!=fa[y][i]){
        x=fa[x][i],y=fa[y][i];
    }
    return fa[x][0];
}
void add(int x,int y){ver[++tot]=y;Next[tot]=lin[x];lin[x]=tot;}
bool cmp(node a,node b){return (a.l/blo==b.l/blo&&a.r<b.r)||(a.l/blo<b.l/blo);}
bool CMP(node a,node b){return a.id<b.id;}
void upd(int x){x=a[x];++cnt[x];if(cnt[x]==1) ans++;}
void del(int x){x=a[x];--cnt[x];if(cnt[x]==0) ans--;}
void update(int x){(v[x]) ? del(x):upd(x);v[x]^=1;}
int main(){
    scanf("%d%d",&n,&m);blo=sqrt(n);
    rep(i,1,n)scanf("%d",&a[i]),b[i]=a[i];
    sort(b+1,b+n+1);
    rep(i,1,n)a[i]=lower_bound(b+1,b+n+1,a[i])-b;
    int x,y;
    rep(i,2,n){
        scanf("%d%d",&x,&y);
        add(x,y),add(y,x);
    }
    dfs(1,1);
    rep(i,1,m){
        scanf("%d%d",&x,&y);
        int top=lca(x,y);
        if(s[x]>s[y])swap(x,y);
        if(x==top)q[i].l=s[x],q[i].r=s[y],q[i].id=i;
        else q[i].l=t[x],q[i].r=s[y],q[i].id=i,q[i].add=top;
    }
    sort(q+1,q+m+1,cmp);
    int l=1,r=0;
    for(int i=1;i<=m;++i){
        while(l<q[i].l) update(pot[l++]);
        while(l>q[i].l) update(pot[--l]);
        while(r<q[i].r) update(pot[++r]);
        while(r>q[i].r) update(pot[r--]);
        if(q[i].add) update(q[i].add);
        q[i].ans=ans;
        if(q[i].add) update(q[i].add);
    }
    sort(q+1,q+m+1,CMP);
    for(int i=1;i<=m;++i) printf("%d\n",q[i].ans);
    return 0;
}

鸣谢StrangeDDDF 这位神仙讲解。

原文地址:https://www.cnblogs.com/kgxw0430/p/10390144.html

时间: 2024-11-05 19:45:55

树上莫队的相关文章

【BZOJ 3735】苹果树 树上莫队(树分块+离线莫队+鬼畜的压行)

学习了树上莫队,树分块后对讯问的$dfs序$排序,然后就可以滑动树链处理答案了. 关于树链的滑动,只需要特殊处理一下$LCA$就行了. 在这里一条树链保留下来给后面的链来转移的$now$的为这条树链上所有点除去$LCA$的颜色种数.因为如果要考虑$LCA$情况就太多了,不如单独考虑$LCA$. 转移后加上当前链的$LCA$进行统计,然后再去掉这个$LCA$更新一下$now$值给后面的链转移. 这都是我的理解,说的有点不清楚,具体请看vfk的题解 OTZ 虽然不是这道题,但是通过这篇博客学习树上莫

(树上莫队)HDU - 5799 This world need more Zhu

题意: 两种询问: 1.询问以u为根的子树中出现的a次的数的和与出现b次的数的和的gcd. 2.询问u到v的树链中出现的a次的数的和与出现b次的数的和的gcd. 有点绕.. 分析: 因为自己不会树上莫队,所以学习了一波. 但是对于子树我还是有所经验,可以转成dfs序来做,之前有做过类似的题,比如这题. 然而对于树链有点懵逼,虽然我觉得也能用dfs序做,不过看大佬们的dfs序做的树链查询,也有点懵,感觉写起来很麻烦. 貌似是修改了dfs序,回溯的时候不再只是和进入时相同的序,而是独立的序. 还是感

[spoj COT2]树上莫队

题目链接:http://www.spoj.com/problems/COT2/ 学会了树上莫队,真的是太激动了!参照博客:http://codeforces.com/blog/entry/43230 讲的十分清楚. #include<bits/stdc++.h> using namespace std; const int MAXN=40005; const int maxm=100005; int cur; int sat[MAXN]; int ean[MAXN]; int A[MAXN*2

【BZOJ-3757】苹果树 块状树 + 树上莫队

3757: 苹果树 Time Limit: 20 Sec  Memory Limit: 256 MBSubmit: 1305  Solved: 503[Submit][Status][Discuss] Description 神犇家门口种了一棵苹果树.苹果树作为一棵树,当然是呈树状结构,每根树枝连接两个苹果,每个苹果都可以沿着一条由树枝构成的路径连到树根,而且这样的路径只存在一条.由于这棵苹果树是神犇种的,所以苹果都发生了变异,变成了各种各样的颜色.我们用一个到n之间的正整数来表示一种颜色.树上

SPOJ COT2 Count on a tree II (树上莫队,倍增算法求LCA)

题意:给一个树图,每个点的点权(比如颜色编号),m个询问,每个询问是一个区间[a,b],图中两点之间唯一路径上有多少个不同点权(即多少种颜色).n<40000,m<100000. 思路:无意中看到树上莫队,只是拿来练练,没有想到这题的难点不在于树上莫队,而是判断LCA是否在两点之间的路径上的问题.耗时1天. 树上莫队的搞法就是: (1)DFS一次,对树进行分块,分成sqrt(n)块,每个点属于一个块.并记录每个点的DFS序. (2)将m个询问区间用所属块号作为第一关键字,DFS序作为第二关键字

bzoj 3757: 苹果树(树上莫队)

3757: 苹果树 Time Limit: 20 Sec  Memory Limit: 256 MB Submit: 1327  Solved: 510 [Submit][Status][Discuss] Description 神犇家门口种了一棵苹果树.苹果树作为一棵树,当然是呈树状结构,每根树枝连接两个苹果,每个苹果都可以沿着一条由树枝构成的路径连到树根,而且这样的路径只存在一条.由于这棵苹果树是神犇种的,所以苹果都发生了变异,变成了各种各样的颜色.我们用一个1到n之间的正整数来表示一种颜色

[BZOJ 3052] [wc2013] 糖果公园 【树上莫队】

题目链接:BZOJ - 3052 题目分析 这道题就是非常经典的树上莫队了,并且是带修改的莫队. 带修改的莫队:将询问按照 左端点所在的块编号为第一关键字,右端点所在的块为第二关键字,位于第几次修改之后为第三关键字 排序. 我们将块的大小设置在 n^(2/3) ,这样一共有 n^(1/3) 个块.最后算法的总复杂度会是 n^(5/3) . 每一次如何从上一个询问转移来呢? 假设上一个询问是 (lx, ly, lt) ,这次的询问是 (x, y, t) .t 代表是在第 t 次修改操作之后. 首先

BZOJ 3757 苹果树 树上莫队

题目大意:给出一棵树,问任意两点之间有多少种不同的颜色,一个人可能会有色盲,会将A和B当成一种颜色. 思路:比较裸的树上莫队,写出来之后,很慢,怀疑是分块的缘故,然后果断找了当年比赛的标称交上去,瞬间rk1,大概看了一眼,他好像是直接用DFS序+曼哈顿距离最小生成树搞的,为什么会比分块快? 昨天下午看到这个题之后就一直在研究树上莫队的正确姿势,然后先写了树分块,后来看了很多牛人的SPOJ COT2的题解,后来又和同学探讨了好久才弄明白. 首先先将树分块,然后把区间排序,按照第一权值为左端点所在块

BZOJ3052 [wc2013] 糖果公园 【树上莫队】

树上莫队和普通的序列莫队很像,我们把树进行dfs,然后存一个长度为2n的括号序列,就是一个点进去当作左括号,出来当作右括号,然后如果访问从u到v路径,我们可以转化成括号序列的区间,记录x进去的时候编号为f[x],出来时为g[x],然后分类讨论一下(f[u]<f[v]),如果u和v的lca不是u,那么就是从g[u]到f[v],否则就是lca的f到另一个点的f,(可以自己试一下,中间过程没有用的点正好就抵消掉了)这里要注意一下,从g[u]到f[v]的时候我们会少掉lca这个点,特殊处理一下即可,然后

【WC2013】 糖果公园 - 树上莫队

[问题描述] Candyland 有一座糖果公园,公园里不仅有美丽的风景.好玩的游乐项目,还有许多免费糖果的发放点,这引来了许多贪吃的小朋友来糖果公园游玩.糖果公园的结构十分奇特,它由 n 个游览点构成,每个游览点都有一个糖果发放处,我们可以依次将游览点编号为 1 至 n.有 n – 1 条 双向道路 连接着这些游览点,并且整个糖果公园都是 连通的 ,即从任何一个游览点出发都可以通过这些道路到达公园里的所有其它游览点.糖果公园所发放的糖果种类非常丰富,总共有 m 种,它们的编号依次为 1至 m.