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

28 篇文章 0 订阅
21 篇文章 0 订阅

#题面

豪哥生活在一个n个点的树形城市里面,每一天都要走来走去。虽然走的是比较的多,但是豪哥在这个城市里面的朋友并不是很多。
当某一天,猴哥给他展现了一下大佬风范之后,豪哥决定要获得一些交往机会来提升交往能力。豪哥现在已经物色上了一条友,打算和它(豪哥并不让吃瓜群众知道性别)交往。豪哥现在spy了一下这个人的所有行程起点和终点,豪哥打算从终点开始走到起点与其相遇。但是豪哥是想找话题的,他想知道以前有多少次行程和此次行程是有交集的,这样豪哥就可以搭上话了。这个路径与之前路径的有交集数量作为豪哥此次的交往机会。
但是豪哥急着要做交往准备,所以算什么交往机会的小事情就交给你了。

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

#分析
让我们来看看树上路径交可以怎么表示。画几棵树后可以发现:
令 l c a ( a , b ) = g , l c a ( c , d ) = f 。 若 树 上 路 径 [ a , b ] , [ c , d ] 有 交 集 , 则 g 在 c d 上    或    f 在 a b 上 。 令lca(a,b)=g,lca(c,d)=f。若树上路径[a,b],[c,d]有交集,则g在cd上~~或~~f在ab上。 lca(a,b)=g,lca(c,d)=f[a,b],[c,d]gcd    fab

这个结论并不是那么好发现(至少我没发现),可以粗略的感性理解一下:
假设我们现在手上是一条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[N],R[N],stm;
int tot,to[N*2],next[N*2],final[N],app[N];
int f[N][19],dep[N];
int t1[N],t2[N],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);
	}
} 
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值