SPOJ COT2 (树上莫队) 传送门 题意: 分析:

题意:

给你一棵大小为$n$的树,每个点都有点权。现在有$m$个询问,每个询问给你一个两个数$a,b$,问你从点$a$到点$b$之间的路径中不同的点权的个数。

分析:

万恶的spoj并没有写点权的数据范围,害我我先re(此题需要离散化点权)

求解带有询问的不同数的个数这类题,~~一看就相当莫队~~ ,但是因为莫队只能够在一个序列上进行操作,因此我们考虑如何让树的树形结构转化为线性的结构。

我们考虑使用树的欧拉序。

我们设$st[]$为第一次遍历经过的编号,$en[]$为回溯时经过的编号,则现在有两种方案:

1. 如果两个节点$a,b$在同一条链上,则我们直接选取欧拉序区间$([st[a],st[b])$
2. 如果两个结点不在同一条链上,则我们直接选取欧拉序区间$(en[a],st[b])$

对于每个区间,我们只需要统计该区间中,数字只出现一次的数字的个数。

与此同时,因为在操作$2$中,两个节点的最近公共祖先是没有被贡献的,因此我们还得加上$LCA(a,b)$的贡献。

之后就是我们普通莫队的分块了。

#include <bits/stdc++.h>
#define maxn 200005
#define K 20
using namespace std;
struct P{
    int l,r,z,id;
}Q[maxn];
struct Node{
    int to,next;
}q[maxn<<1];
int lim,Cnt,pos[maxn<<1],c[maxn];
int n,m,loc[maxn<<1],dfn,st[maxn],en[maxn],d[maxn],f[maxn][25];
int ans[maxn],cnt[maxn],sum,head[maxn];
bool vis[maxn];
bool cmp(const P&a,const P&b){
    return pos[a.l]==pos[b.l]?a.r<b.r:pos[a.l]<pos[b.l];
}
void add_edge(int from,int to){
    q[Cnt].to=to;
    q[Cnt].next=head[from];
    head[from]=Cnt++;
}
void dfs(int x){
    loc[st[x]=++dfn]=x;
    vis[x]=1;
    for(int i=1;i<=K;i++) f[x][i]=f[f[x][i-1]][i-1];
    for(int i=head[x];i!=-1;i=q[i].next){
        int to=q[i].to;
        if(!vis[to]) d[to]=d[f[to][0]=x]+1,dfs(to);
    }
    loc[en[x]=++dfn]=x;
}
int lca(int x,int y){
    if(x==y)return x;
    if(d[x]<d[y])swap(x,y);
    for(int i=K;~i;i--) if(d[f[x][i]]>=d[y])x=f[x][i];
    if(x==y) return x;
    for(int i=K;~i;i--){
        if(f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i];
    }
    return f[x][0];
}
void deal(int x){
    if(!vis[x]){
        if(!(--cnt[c[x]])) sum--;
    }
    else if(!(cnt[c[x]]++)) sum++;
    vis[x]^=1;
}
int D[maxn];
int main()
{
    scanf("%d%d",&n,&m);
    memset(head,-1,sizeof(head));
    Cnt=0;
    for(int i=1;i<=n;i++){
        scanf("%d",&c[i]);
        D[i]=c[i];
    }
    sort(D+1,D+1+n);
    for(int i=1;i<=n;i++) c[i]=lower_bound(D+1,D+1+n,c[i])-D;
    for(int i=1;i<n;i++){
        int x,y;
        scanf("%d%d",&x,&y);
        add_edge(x,y),add_edge(y,x);
    }
    dfs(d[1]=1),lim=(int)sqrt(n*2+0.5);
    for(int i=1;i<=dfn;i++) pos[i]=(i-1)/lim+1;
    for(int i=1;i<=m;i++){
        int x,y;
        scanf("%d%d",&x,&y);
        Q[i].id=i;
        if(st[x]>st[y]) swap(x,y);
        int z=lca(x,y);
        if(z==x) Q[i].l=st[x],Q[i].r=st[y];
        else Q[i].l=en[x],Q[i].r=st[y],Q[i].z=z;
    }
    sort(Q+1,Q+1+m,cmp);
    int l=1,r=0;
    for(int i=1;i<=m;i++){
        while(r<Q[i].r) deal(loc[++r]);
        while(r>Q[i].r) deal(loc[r--]);
        while(l<Q[i].l) deal(loc[l++]);
        while(l>Q[i].l) deal(loc[--l]);
        if(Q[i].z) deal(Q[i].z);
        ans[Q[i].id]=sum;
        if(Q[i].z) deal(Q[i].z);
    }
    for(int i=1;i<=m;i++) printf("%d
",ans[i]);
}