bzoj 3757: 苹果树(树上莫队)
2016-05-09 19:40
375 查看
3757: 苹果树
Time Limit: 20 Sec Memory Limit: 256 MBSubmit: 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 31 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
21
2
HINT
0<=x,y,a,b<=NN<=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]); }
相关文章推荐
- Unity学习笔记13——代码动态加载Prefab预设体
- 29.把数组排成最小的数
- BufferedOutputStream的缓存功能解析(源码阅读)
- 第十、十一周项目1 - 点-圆-圆柱类族的设计(2)
- Spring声明式事务配置管理方法
- http协议
- Barn Repair
- AndroidStudio 快捷键大全
- Java之Callable Future FutureTask Exectuor使用笔记
- JAVA基础之——深入理解Java的接口和抽象类
- 安卓UI构架解读1
- Dockerfile中CMD与ENTRYPOINT的区别
- thinkphp生成的验证码不显示问题解决
- CentOS下Hive2.0.0集群模式安装详解
- hihoCoder #1040 (判断是否为矩形)
- 安卓中的消息循环机制Handler及Looper详解
- 面向对象(上)知识点
- "open failed: EACCES (Permission denied)"权限已加,写入sd卡仍报错的解决办法
- 使用Docker容器不能忽略的10件事
- asynchttpclient post方法使用