题面:
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;
}