题目描述
-
神犇家门口种了一棵苹果树。苹果树作为一棵树,当然是呈树状结构,每根树枝连接两个苹果,每个苹果都可以沿着一条由树枝构成的路径连到树根,而且这样的路径只存在一条。由于这棵苹果树是神犇种的,所以苹果都发生了变异,变成了各种各样的颜色。我们用一个1到N之间的正整数来表示一种颜色。树上一共有N个苹果。每个苹果都被编了号码,号码为一个1到N之间的正整数。我们用0代表树根。只会有一个苹果直接连到树根。
-
有许许多多的人来神犇家里膜拜神犇。可神犇可不是随便就能膜拜的。前来膜拜神犇的人需要正确回答一个问题,才能进屋膜拜神犇。这个问题就是,从树上编号为N的苹果出发,由树枝走到编号为N的苹果,路径上经过的苹果一共有多少种不同的颜色(包括苹果u和苹果v的颜色)?不过神犇注意到,有些来膜拜的人患有色盲症。具体地说,一个人可能会认为颜色a就是颜色b,那么他们在数苹果的颜色时,如果既出现了颜色a的苹果,又出现了颜色b的苹果,这个人只会算入颜色b,而不会把颜色a算进来。
-
神犇是一个好人,他不会强人所难,也就会接受由于色盲症导致的答案错误(当然答案在色盲环境下也必须是正确的)。不过这样神犇也就要更改他原先数颜色的程序了。虽然这对于神犇来说是小菜一碟,但是他想考验一下你。你能替神犇完成这项任务吗?
数据范围
题目分析
- 听说又是一道鬼畜的模板题。
- 这道题
显然使用树上莫队。 - 普通莫队是在序列上做的,如何转移到树上呢?
- 最直接的思路:把树强制转成序列。
- 恭喜你,你猜对了。
树转序列
- 采用一种神奇的东西(具体叫什么序也不清楚)。
- 就是在遍历一棵树的时候,如果递归到了这个点,把它扔进序列中,如果回溯,再一次把它扔进序列中。
- 盗个图:
- 那么这个序列就是
- 我们发现,对于一条路径 ,我们分类讨论。
- 先设 为 这个点第一次在序列中的位置, 表示的是第二次,也是最后一次。
- 若 与 中一个为它们的 ,那么它们的路径就是序列 中只出现过一次的点。
- 例如 ,序列为
- 其中出现过一次的为 。
- 而若 与 均不为 ,那么它们的路径就是序列 中只出现过一次的点。
- 例如 ,序列为 。
- 其中出现过一次的为 。
- 我们发现这里面没有包含它们的 —— 点,这要特殊处理。
- 然后就把树上路径转化为了序列上的一段区间,用普通莫队的方法处理即可。
代码
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;
const int N=5e4+100,M=1e5+100,maxn=16;
struct node{
int x,y,next;
}a[2*N];int len,last[N];
struct ques{
int x,y,id,a,b,lca;
}tr[M];
bool bk[N];int st[N],ed[N];
int sum=0;int fa[N][20],Be[2*N];
bool cmp(ques n1,ques n2){
if(Be[n1.x]!=Be[n2.x]) return (Be[n1.x]<Be[n2.x]);
else return (n1.y<n2.y);
}
int coun[N],count2[N],g[2*N],deep[N];
void ins(int x,int y){
a[++len].x=x;a[len].y=y;
a[len].next=last[x];last[x]=len;
}
void dfs(int x){
bk[x]=false;st[x]=++sum;g[sum]=x;
for(int i=1;i<=maxn;i++) fa[x][i]=fa[fa[x][i-1]][i-1];
for(int k=last[x];k;k=a[k].next){
int y=a[k].y;
if(bk[y]) fa[y][0]=x,deep[y]=deep[x]+1,dfs(y);
}
ed[x]=++sum;g[sum]=x;
}
int co[N];
int findlca(int x,int y){
for(int i=maxn;i>=0;i--){
if(deep[fa[x][i]]>=deep[y]) x=fa[x][i];
if(deep[fa[y][i]]>=deep[x]) y=fa[y][i];
}
for(int i=maxn;i>=0;i--){
if(fa[x][i]!=fa[y][i])
x=fa[x][i],y=fa[y][i];
}
if(x!=y) x=fa[x][0],y=fa[y][0];
return x;
}
int Ans[M];
int main()
{
int n,m;scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) scanf("%d",&co[i]);
len=0;memset(last,0,sizeof(last));
for(int i=1;i<=n;i++){
int x,y;
scanf("%d%d",&x,&y);
ins(x,y);ins(y,x);
}
memset(bk,true,sizeof(bk));
dfs(0);
int T=sqrt(2*n);
for(int i=1;i<=2*n;i++) Be[i]=(i-1)/T+1;
for(int i=1;i<=m;i++){
int x,y;scanf("%d%d",&x,&y);
scanf("%d%d",&tr[i].a,&tr[i].b);
if(st[x]>st[y]) {int t=x;x=y;y=t;}
int lca=findlca(x,y);tr[i].lca=lca;
if(lca==x||y==lca) tr[i].x=st[x],tr[i].y=st[y];
else tr[i].x=ed[x],tr[i].y=st[y];
tr[i].id=i;
}
sort(tr+1,tr+m+1,cmp);
int L=1,R=1,ans=0;coun[co[g[1]]]++;count2[g[1]]++;
for(int i=1;i<=m;i++){
while(L>tr[i].x){
count2[g[L-1]]++;
if(count2[g[L-1]]==2){
if(coun[co[g[L-1]]]==1&&g[L-1]) ans--;
coun[co[g[L-1]]]--;
}
else{
if(coun[co[g[L-1]]]==0&&g[L-1]) ans++;
coun[co[g[L-1]]]++;
}
L--;
}
while(R<tr[i].y){
count2[g[R+1]]++;
if(count2[g[R+1]]==2){
if(coun[co[g[R+1]]]==1&&g[R+1]) ans--;
coun[co[g[R+1]]]--;
}
else{
if(coun[co[g[R+1]]]==0&&g[R+1]) ans++;
coun[co[g[R+1]]]++;
}
R++;
}
while(L<tr[i].x){
count2[g[L]]--;
if(count2[g[L]]==0){
if(coun[co[g[L]]]==1&&g[L]) ans--;
coun[co[g[L]]]--;
}
else{
if(coun[co[g[L]]]==0&&g[L]) ans++;
coun[co[g[L]]]++;
}
L++;
}
while(R>tr[i].y){
count2[g[R]]--;
if(count2[g[R]]==0){
if(coun[co[g[R]]]==1&&g[R]) ans--;
coun[co[g[R]]]--;
}
else{
if(coun[co[g[R]]]==0&&g[R]) ans++;
coun[co[g[R]]]++;
}
R--;
}
int t=ans;int LCA=co[tr[i].lca];
if(!coun[LCA]) ans++;coun[LCA]++;
if(tr[i].a!=tr[i].b&&coun[tr[i].a]&&coun[tr[i].b]) ans--;
Ans[tr[i].id]=ans;ans=t;coun[LCA]--;
}
for(int i=1;i<=m;i++) printf("%d\n",Ans[i]);
return 0;
}