您的位置:首页 > 其它

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

2016-05-09 19:40 375 查看

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<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define N 50003
using namespace std;
int n,m,sum,top;
int c
,dfsn
,v[N*2],next[N*2],point
,deep
,root,sz,block;
int fa
[20],mi[20],vis
,num
,ans
,cnt,tot,belong
,st
;
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]);
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: