SPOJ---10707:Count on a tree II【树上莫队】

题面:

SPOJ---10707:Count on a tree ll

题意:

给定一颗树,M次查询任意两点之间的最短路径上有多少个不同的数

分析:

如果是在序列上,那就直接莫队,所以只要把树拍成线性的就可以跑莫队了;这就要有请欧拉序来帮忙:

上图的欧拉序为:1 2 3 3 4 4 5 5 2 6 7 7 8 8 6 1【也叫括号序列,这与求LCA的欧拉序不同】

记st[x]表示x在欧拉序中第一次出现的时间(下标) ,ed[x]表示x在欧拉序中最后出现的时间,现在查询【u,v】对应的序列(假设st[u] < st[v]):

(1)LCA(u,v) = u,那么u到v的最短路径在欧拉序中对应的区间为【st[u],st[v]】

(2)LCA(u,v) != u, 那么u到v的最短路径在欧拉序中对应的区间为【ed[u],st[v]】加上 LCA(u,v)这个点

查询【1,4】在欧拉序中对应【1 2 3 3 4】,忽略出现两次的点,因为进入了3的子树又出来,最短路就不会走这个点

查询【4,7】在欧拉序中对应【4 5 5 2 6 7】,发现少了LCA(4,7)这个点,加上就好了,这也不难理解

(1)处理出现两次的节点:莫队双指针移动时,同时记录每个节点对当前区间是否有贡献;如果有贡献,那么就删去这个节点的贡献(出现了2次),否则增加其贡献

(2)处理lca节点的贡献:如果需要考虑lca,那么先加上lca的贡献,再减去lca的贡献(因为lca只影响当前询问区间)

此题,每个节点的权值过大,可以先离散化再标记,求LCA有很多方法,我用的树链剖分,分块普通排序T了,奇偶排序跑过

代码:

#include <bits/stdc++.h>

using namespace std;
typedef long long LL;
const int maxn = 1e5+15;
int cnt,tt,head[maxn];
struct edge{
    int to,nxt;
}e[maxn<<1];
inline int read(){
    int x = 0, f = 1;
    char c = getchar();
    for (; !isdigit(c); c = getchar()) if (c == '-') f = -1;
    for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
    return x * f;
}
inline void write(int x)
{
     if(x<0) putchar('-'),x=-x;
     if(x>9) write(x/10);
     putchar(x%10+'0');
}
inline void addedge(int u,int v){
    e[++cnt] = (edge){v,head[u]};
    head[u] = cnt;
}
int d[maxn],f[maxn],sz[maxn],son[maxn],st[maxn],ed[maxn],b[maxn];
void dfs1(int x,int fa,int depth){
     f[x] = fa; d[x] = depth; 
     sz[x] = 1; st[x] = ++tt; b[tt] = x;
     for(int i = head[x];i;i = e[i].nxt){
        int v = e[i].to;
        if(v == fa) continue;
        dfs1(v,x,depth+1); sz[x] += sz[v];
        if(sz[v] > sz[son[x]]) son[x] = v;
     }
     ed[x] = ++tt; b[tt] = x;
}
int rk[maxn],id[maxn],top[maxn],num;
void dfs2(int x,int Top){
    top[x] = Top; id[x] = ++num;
    rk[num] = x;
    if(!son[x]) return;
    dfs2(son[x],Top);
    for(int i = head[x];i;i = e[i].nxt){
        int v = e[i].to;
        if(v != son[x]&&v != f[x]) dfs2(v,v);
    }
}
int LCA(int u,int v){
    while(top[u] != top[v]){
        if(d[top[u]] < d[top[v]]) swap(u,v);
        u = f[top[u]];
    }
    return d[u] < d[v] ? u : v;
}
int n,m,u,v,block,a[maxn],c[maxn],ans[maxn];
struct query{
    int l,r,lca,op;
}q[maxn];
int use[maxn],vis[maxn],res;
inline void add(int x){
    if(++vis[x] == 1) res++;
}
inline void del(int x){
    if(--vis[x] == 0) res--;
}
void Deal(int x) {
    use[x] ? del(a[x]) : add(a[x]); use[x] ^= 1;
}
bool cmp(query a,query b){
    //return a.l/block == b.l/block ? a.l<b.l : a.r<b.r;
    return (a.l/block)^(b.l/block)?a.l<b.l:(((a.l/block)&1)?a.r<b.r:a.r>b.r);
}
void init(){
    block = sqrt(2.0*n);
    sort(q,q+m,cmp);
    sort(c+1,c+n+1);
    int len = unique(c+1,c+n+1)-c;
    for(int i = 1;i <= n; ++i) a[i] = lower_bound(c+1,c+len,a[i])-c;
}
int main(){
    scanf("%d %d",&n,&m);
    for(int i = 1;i <= n; ++i) a[i]=read(),c[i]=a[i];
    for(int i = 1;i < n; ++i){
        u = read(),v = read();
        addedge(u,v); addedge(v,u);
    }
    dfs1(1,0,1);dfs2(1,1);
    for(int i = 0;i < m; ++i){
        u = read(),v = read();
        if(st[u] > st[v]) swap(u,v);
        int lca = LCA(u,v);
        if(lca == u) q[i] = (query){st[u],st[v],0,i};
        else q[i] = (query){ed[u],st[v],lca,i};
    }
    init();
    int L = 1,R = 0; 
    for(int i = 0;i < m; ++i){
        while(R < q[i].r) ++R,Deal(b[R]);
        while(R > q[i].r) Deal(b[R]),--R;
        while(L < q[i].l) Deal(b[L]),++L;
        while(L > q[i].l) --L,Deal(b[L]);
        if(q[i].lca) Deal(q[i].lca);
        ans[q[i].op] = res;
        if(q[i].lca) Deal(q[i].lca);
    }
    for(int i = 0;i < m; ++i) write(ans[i]),putchar('\n');
    return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值