神犇家门口种了一棵苹果树。苹果树作为一棵树,当然是呈树状结构,每根树枝连接两个苹果,每个苹果都可以沿着一条由树枝构成的路径连到树根,而且这样的路径只存在一条。由于这棵苹果树是神犇种的,所以苹果都发生了变异,变成了各种各样的颜色。我们用一个1到n之间的正整数来表示一种颜色。树上一共有n个苹果。每个苹果都被编了号码,号码为一个1到n之间的正整数。我们用0代表树根。只会有一个苹果直接与根相连。
有许许多多的人来神犇家里膜拜神犇。可神犇可不是随便就能膜拜的。前来膜拜神犇的人需要正确回答一个问题,才能进屋膜拜神犇。这个问题就是,从树上编号为u的苹果出发,由树枝走到编号为v的苹果,路径上经过的苹果一共有多少种不同的颜色(包括苹果u和苹果v的颜色)?不过神犇注意到,有些来膜拜的人患有色盲症。具体地说,一个人可能会认为颜色a就是颜色b,那么他们在数苹果的颜色时,如果既出现了颜色a的苹果,又出现了颜色b的苹果,这个人只会算入颜色b,而不会把颜色a算进来。
神犇是一个好人,他不会强人所难,也就会接受由于色盲症导致的答案错误(当然答案在色盲环境下也必须是正确的)。不过这样神犇也就要更改他原先数颜色的程序了。虽然这对于神犇来说是小菜一碟,但是他想考验一下你。你能替神犇完成这项任务吗?
输出一共m行,每行仅包含一个整数,代表这个人应该数出的颜色种数。
题解:树上莫队。
首先要对树上的点进行分块,分块方式与上一题bzoj 1086 相同。
然后对于每个询问以左端点所在的块为第一关键字,右端点的dfs序为第二关键字排序。
那么如何进行区间的转移呢?
网上有个神奇的证明:
知道这个T(targetV, curU)= T(curV, curU) xor T(curV, targetV)结论就可以每次进行转移了。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<cmath> #define N 50003 using namespace std; int n,m,sum,top; int c[N],dfsn[N],v[N*2],next[N*2],point[N],deep[N],root,sz,block; int fa[N][20],mi[20],vis[N],num[N],ans[N],cnt,tot,belong[N],st[N]; struct data{ int a,b,u,v,id; }q[100003]; int cmp(data a,data b) { if (belong[a.u]==belong[b.u]) return dfsn[a.v]<dfsn[b.v]; return belong[a.u]<belong[b.u]; } void add(int x,int y) { tot++; next[tot]=point[x]; point[x]=tot; v[tot]=y; tot++; next[tot]=point[y]; point[y]=tot; v[tot]=x; } int dfs(int x,int f) { dfsn[x]=++sz; int size=0; for (int i=1;i<=16;i++) { if (deep[x]-mi[i]<0) break; fa[x][i]=fa[fa[x][i-1]][i-1]; } for (int i=point[x];i;i=next[i]) if (v[i]!=f) { deep[v[i]]=deep[x]+1; fa[v[i]][0]=x; size+=dfs(v[i],x); if (size>=block)//分块 { cnt++; for (int j=1;j<=size;j++) belong[st[top--]]=cnt; size=0; } } st[++top]=x; return size+1; } int lca(int x,int y)//求最近公共祖先 { if (deep[x]<deep[y]) swap(x,y); int k=deep[x]-deep[y]; for (int i=0;i<17;i++) if (k>>i&1) x=fa[x][i]; if (x==y) return x; for (int i=16;i>=0;i--) if (fa[x][i]!=fa[y][i]) x=fa[x][i],y=fa[y][i]; return fa[x][0]; } void reserve(int x)//将点的存在性取反 { if (!vis[x]) { vis[x]=1; num[c[x]]++; if (num[c[x]]==1) sum++; } else { vis[x]=0; num[c[x]]--; if (num[c[x]]==0) sum--; } } void solve(int x,int y)//更改x,y路径上除lca以外的点的存在性 { while (x!=y) { if (deep[x]>deep[y]) { reserve(x); x=fa[x][0]; } else { reserve(y); y=fa[y][0]; } } } int main() { scanf("%d%d",&n,&m); for (int i=1;i<=n;i++) scanf("%d",&c[i]); for (int i=1;i<=n;i++) { int x,y; scanf("%d%d",&x,&y); if (!x) root=y;//因为0是没有苹果的,所有我们要寻找有苹果的根 else if (!y) root=x; else add(x,y); } mi[0]=1; block=sqrt(n); for (int i=1;i<=16;i++) mi[i]=mi[i-1]*2; deep[root]=1; dfs(root,0); ++cnt; while (top) belong[st[top--]]=cnt; for (int i=1;i<=m;i++) { scanf("%d%d%d%d",&q[i].u,&q[i].v,&q[i].a,&q[i].b); if (dfsn[q[i].u]>dfsn[q[i].v]) swap(q[i].u,q[i].v); q[i].id=i; } sort(q+1,q+m+1,cmp); int t=lca(q[1].u,q[1].v); solve(q[1].u,q[1].v); reserve(t); ans[q[1].id]=sum; if (num[q[1].a]&&num[q[1].b]&&q[1].a!=q[1].b) ans[q[1].id]--;//色盲特判 reserve(t); for (int i=2;i<=m;i++) { solve(q[i-1].u,q[i].u); solve(q[i-1].v,q[i].v); int t=lca(q[i].u,q[i].v); reserve(t); ans[q[i].id]=sum; if (num[q[i].a]&&num[q[i].b]&&q[i].a!=q[i].b) ans[q[i].id]--; reserve(t); } for (int i=1;i<=m;i++) printf("%d\n",ans[i]); }