您的位置:首页 > 运维架构

jzoj5290 【NOIP2017提高组A组模拟8.17】行程的交集 (树上路径交,dfs序+树状数组维护姿势)

2017-08-17 16:29 344 查看

题面

豪哥生活在一个n个点的树形城市里面,每一天都要走来走去。虽然走的是比较的多,但是豪哥在这个城市里面的朋友并不是很多。

当某一天,猴哥给他展现了一下大佬风范之后,豪哥决定要获得一些交往机会来提升交往能力。豪哥现在已经物色上了一条友,打算和它(豪哥并不让吃瓜群众知道性别)交往。豪哥现在spy了一下这个人的所有行程起点和终点,豪哥打算从终点开始走到起点与其相遇。但是豪哥是想找话题的,他想知道以前有多少次行程和此次行程是有交集的,这样豪哥就可以搭上话了。这个路径与之前路径的有交集数量作为豪哥此次的交往机会。

但是豪哥急着要做交往准备,所以算什么交往机会的小事情就交给你了。

对于另外50%的数据n,m≤200000

分析

让我们来看看树上路径交可以怎么表示。画几棵树后可以发现:

令lca(a,b)=g,lca(c,d)=d。若树上路径[a,b],[c,d]有交集,则g在cd上 或 d在ab上。

这个结论并不是那么好发现(至少我没发现),可以粗略的感性理解一下:

假设我们现在手上是一条lca深度大的路径,这条路径最高可达点就是lca,若lca不在另一条路径上,又因为他lca深度大,所以不可能在另外一条路径lca上方,所以就没有可达点了。

记住这个姿势,以后会用到。

那么问题就变成了,在ab上找之前路径的lca 与 在找覆盖lca的之前路径。

整体地看,这两种情况是没有重合的,因为若ab的lca在cd路径上,则ab不可能经过cd的lca.

极端情况,容易发现当lca相等的时候需要特判。

Part1: ab上找之前路径的lca

静态树考虑dfs序。 (动态树考虑欧拉序)

对于一个点x,a->lca(a,b)这样的树链如果经过他,则一定是从它的子树走向它的祖先。

然后就比较显然了,每多加一个这样的x,则给他子树中所有点的权值都+1.

查询(a,lca(a,b),b)的时候,就用a权+b权-lca权*2.

若a与lca在x的上下两方,则会被累加到贡献。

若同时在x的下方,则会被lca权减掉。

Part2: 覆盖lca的之前路径

同样地考虑dfs序,

很方便的树上前缀和。若有一条路径(a,b),则给lca(a,b) +1,a-1,b-1.然后查询一个点到根的权值和,这是离线时的套路。

但这题这样不好做,因为不能每一次都重构树,于是我们变一下,变成类似树上后缀和的东西。

对于一条路径(a,b),给a+1,b+1,lca-2.

一个点的子树权值和就是他的权值。

用两树状数组维护一下dfs序上的信息,我们就把这道题给搞定了。

这题的坑点主要在于,两个树状数组维护的信息意义是不一样的,压根不可以相提并论。

Demo

#include <cstdio>
#include <iostream>
#define lowbit(x) ((x)&-(x))
#define treeSum(t,x) (sum(t,R[x])-sum(t,L[x]-1))
using namespace std;
const int N=2e5+10;
int n,m,L
,R
,stm;
int tot,to[N*2],next[N*2],final
,app
;
int f
[19],dep
;
int t1
,t2
,ans;
void change(int *tr,int x,int v) {for (; x<=n; x+=lowbit(x)) tr[x]+=v;}
int sum(int *tr,int x) {int ret=0; for (; x; x-=lowbit(x)) ret+=tr[x]; return ret;}

void link(int x,int y) {
to[++tot]=y,next[tot]=final[x],final[x]=tot;
}
void dfs(int x,int fa) {
L[x]=++stm;
f[x][0]=fa; for (int i=1; i<19; i++) f[x][i]=f[f[x][i-1]][i-1];
dep[x]=dep[fa]+1;
for (int i=final[x]; i; i=next[i]) if (to[i]!=fa) dfs(to[i],x);
R[x]=stm;
}
int lca(int x,int y) {
if (dep[x]<dep[y]) swap(x,y);
for (int i=18; i>=0; i--) if (dep[f[x][i]]>=dep[y]) x=f[x][i];
if (x==y) return x;
for (int i=18; i>=0; i--) if (f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i];
return f[x][0];
}
void calcAns(int x,int y,int lca) {
ans+=treeSum(t1,lca);
ans+=sum(t2,L[x])+sum(t2,L[y])-sum(t2,L[lca])*2;
}
int main() {
freopen("3.in","r",stdin);
cin>>n;
for (int i=1; i<n; i++) {
int u,v; scanf("%d %d\n",&u,&v); link(u,v),link(v,u);
}
dfs(1,0);
cin>>m;
for (int i=1; i<=m; i++) {
int u,v; scanf("%d %d\n",&u,&v);
int g=lca(u,v);
ans=0;
calcAns(u,v,g);
printf("%d\n",ans+app[g]);
app[g]++;
change(t1,L[u],1);
change(t1,L[v],1);
change(t1,L[g],-2);

change(t2,L[g],1);
change(t2,R[g]+1,-1);
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: