bzoj 3757: 苹果树(树上莫队)

3757: 苹果树

Time Limit: 20 Sec  Memory Limit: 256 MB
Submit: 1327  Solved: 510
[ Submit][ Status][ Discuss]

Description

    神犇家门口种了一棵苹果树。苹果树作为一棵树,当然是呈树状结构,每根树枝连接两个苹果,每个苹果都可以沿着一条由树枝构成的路径连到树根,而且这样的路径只存在一条。由于这棵苹果树是神犇种的,所以苹果都发生了变异,变成了各种各样的颜色。我们用一个1到n之间的正整数来表示一种颜色。树上一共有n个苹果。每个苹果都被编了号码,号码为一个1到n之间的正整数。我们用0代表树根。只会有一个苹果直接与根相连。

有许许多多的人来神犇家里膜拜神犇。可神犇可不是随便就能膜拜的。前来膜拜神犇的人需要正确回答一个问题,才能进屋膜拜神犇。这个问题就是,从树上编号为u的苹果出发,由树枝走到编号为v的苹果,路径上经过的苹果一共有多少种不同的颜色(包括苹果u和苹果v的颜色)?不过神犇注意到,有些来膜拜的人患有色盲症。具体地说,一个人可能会认为颜色a就是颜色b,那么他们在数苹果的颜色时,如果既出现了颜色a的苹果,又出现了颜色b的苹果,这个人只会算入颜色b,而不会把颜色a算进来。

神犇是一个好人,他不会强人所难,也就会接受由于色盲症导致的答案错误(当然答案在色盲环境下也必须是正确的)。不过这样神犇也就要更改他原先数颜色的程序了。虽然这对于神犇来说是小菜一碟,但是他想考验一下你。你能替神犇完成这项任务吗?

Input

输入第一行为两个整数n和m,分别代表树上苹果的个数和前来膜拜的人数。
接下来的一行包含n个数,第i个数代表编号为i的苹果的颜色Coli。
接下来有n行,每行包含两个数x和y,代表有一根树枝连接了苹果x和y(或者根和一个苹果)。
接下来有m行,每行包含四个整数u、v、a和b,代表这个人要数苹果u到苹果v的颜色种数,同时这个人认为颜色a就是颜色b。如果a=b=0,则代表这个人没有患色盲症。

Output

输出一共m行,每行仅包含一个整数,代表这个人应该数出的颜色种数。

Sample Input

5 3
1 1 3 3 2
0 1
1 2
1 3
2 4
3 5
1 4 0 0
1 4 1 3
1 4 1 2

Sample Output

2
1
2

HINT

0<=x,y,a,b<=N

N<=50000

1<=U,V,Coli<=N

M<=100000

Source

[ Submit][ Status][ Discuss]


题解:树上莫队。

首先要对树上的点进行分块,分块方式与上一题bzoj 1086 相同。

然后对于每个询问以左端点所在的块为第一关键字,右端点的dfs序为第二关键字排序。

那么如何进行区间的转移呢?

网上有个神奇的证明:

用S(v, u)代表 v到u的路径上的结点的集合。
用root来代表根结点,用lca(v, u)来代表v、u的最近公共祖先。
那么
S(v, u) = S(root, v) xor S(root, u) xor lca(v, u)
其中xor是集合的对称差。
简单来说就是 节点出现两次消掉。
lca很讨厌,于是再定义
T(v, u) = S(root, v) xor S(root, u)  //表示的就是u到v除去lca的路径
观察将curV移动到targetV前后T(curV, curU)变化:
T(curV, curU) = S(root, curV) xor S(root, curU)
T(targetV, curU) = S(root, targetV) xor S(root, curU)
取对称差:
T(curV, curU) xor T(targetV, curU)= (S(root, curV) xor S(root, curU)) xor (S(root, targetV) xor S(root, curU))
由于对称差的交换律、结合律:
T(curV, curU) xor T(targetV, curU)= S(root, curV) xorS(root, targetV)
两边同时xor T(curV, curU):
T(targetV, curU)= T(curV, curU) xor S(root, curV) xor S(root, targetV)
发现最后两项很爽……哇哈哈
T(targetV, curU)= T(curV, curU) xor T(curV, targetV)
(有公式恐惧症的不要走啊 T_T)
也就是说,更新的时候,xor T(curV, targetV)就行了。
即,对curV到targetV路径(除开lca(curV, targetV))上的结点,将它们的存在性取反即可。
(做的时候维护T,然后计算的时候在加上LCA就可以了)

知道这个T(targetV, curU)= T(curV, curU) xor T(curV, targetV)结论就可以每次进行转移了。

#include
#include
#include
#include
#include
#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]=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]>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]);
}



你可能感兴趣的:(分块,莫队)