2018.10.05【校内模拟】相遇(DFS序)(树状数组)

版权声明:转载请声明出处,谢谢配合。 https://blog.csdn.net/zxyoi_dreamer/article/details/82946160

【问题描述】

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

【输入】

第一行一个正整数 n表示节点个数。接下来 n-1行,每行两个正整数分别是 u,v表示节点
u和 v之间有连边。接下来一行一个 正整数 m表示路径个数。然后有 m行,每行两个正整
数分别是u,v分别表示 u到v之间有一条路径。

【输出】

输出共m行,每行一个整数,第 i行表示豪哥在这条路径上获得的交往机会。

【输入样例】

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

【输出样例】

0
1
2
2

【数据范围与约定】

对于20%的数据n,m≤2000
对于另外20%的数据n,m≤50000
对于另外10%的数据n,m≤200000保证树形结构是一条链
对于另外50%的数据n,m≤200000


解析:

注意这里路径相交不是边相交,而是点相交。

D F S DFS 序的妙用。显然这道题应该都会树链剖分写,但是这道题用 D F S DFS 序会少一个 l o g log 的复杂度

思路:

树链剖分的写法我就不能赘述了,直接步入主题。

首先,我们要了解一个性质,这是我们这道题用 D F S DFS 序乱搞的关键。

树上两个路径有交,当且仅当两个路径其中之一的深度最浅点(即起终点 L c a Lca )在另一条路径上。


简单证明一下:

考虑两条路径 < u , v > <u,v> < a , b > <a,b> ,不失一般性地规定 L c a ( u , v ) = g Lca(u,v)=g 的深度大于 L c a ( a , b ) = c Lca(a,b)=c 的深度。

显然当 c c 的某个祖先不是 g g 的祖先的时候,两条路径不在该祖先的同一子树中,当然没有交。

由此推出, c c 必然在 < g , r o o t > <g,root> 这条路径上,由于 g g < u , v > <u,v> 路径的深度最小的点,所以 c c 显然不在 < u , v > <u,v> 路径上。下面证明当两条路径有交时, g g 在路径 < a , b > <a,b> 上。

因为 c c 深度较小,那么要从 c c 延伸出一条路径与 < u , v > <u,v> 有交,必然进入其 L c a Lca 的子树,即必然经过 g g

而如果 g g 在路径 < a , b > <a,b> 上则两条路径直接满足交的定义。


那么我们就可以用这个结论搞事情。(树链剖分也是建立在这个结论的基础之上)

我们将贡献出的答案分开统计。
第一、之前边的 L c a Lca 在现在给出的路径上;
第二、现在给出的路径的 L c a Lca 在之前的路径上;

注意会有一种情况被记重复,就是两条路径的 L c a Lca 重合。
我们直接处理一下,在统计前两种的时候不考虑 L c a Lca ,最后将 L c a Lca 重合的贡献直接加起来,这个显然是 O ( 1 ) O(1) 的,我们只需要记录每个点作为前面的路径的 L c a Lca 的次数就行了。

下面讲一讲怎么处理上面两种答案。
讲到询问,肯定我们要先确定维护方式,才能确定我们怎么处理询问。


下面分情况讨论;

情况一:在现在的路径上找之前的 L c a Lca

想一想, d f s dfs 序最擅长处理的问题是什么?
子树问题!
那么我们将这个问题转化到子树上考虑。

对于每一个 L c a Lca ,我们将它子树里面的所有节点权值 + 1 +1 那么这时候每个节点权值代表的就是它到根节点路径上有几个之前的 L c a Lca 。好好体会一下这个神奇的性质。

那么我们要询问 < u , v > <u,v> 这条路径上有几个之前的 L c a Lca ,由于我们之前说了不考虑 L c a Lca 除的贡献,最后处理,我们将询问拆分成两部分 < u , L c a > <u,Lca> < v , L c a > <v,Lca> (都不包含 L c a Lca )分开统计,差分。最终情况一的贡献就是 v + u L c a × 2 v+u-Lca\times 2

情况二:找现在的 L c a Lca 在几条之前的路径上

第一步转化成子树问题。
很方便的树上前缀和。若有一条路径 ( a , b ) (a,b) ,则给 l c a ( a , b ) + 1 lca(a,b) +1 a 1 a-1 b 1 b-1 。然后查询一个点到根的权值和,这是离线时的套路。

在线改为子树和就行了。
我们对于每一条路径,在起终点 u , v u,v 的节点权值 + 1 +1 ,在 L c a Lca 处将节点权值 2 -2 ,这时候一棵子树里面 + 1 +1 的个数就是有多少个路径的起终点在这颗子树里面, 2 -2 的个数就是有多少个路径的 L c a Lca 在这颗子树里面。
若一个路径被某棵子树完全包含或完全不包含,则这条路径对这颗子树的贡献是 0 0 (显然),有贡献就必然是 + 1 +1 ,当且仅当一个起点在子树中,另一个不在,则 L c a Lca 必然不在此子树中,那么这条路径必然穿过这个子树的根。

那么情况二的贡献就是路径 L c a Lca 的子树和


那么最后加上 L c a Lca 重合的贡献就是答案。

发现这里两种情况的更新和查询分别对应
1.区间加,单点查询
2.单点加,区间查询

显然可以直接封装树状数组。


代码:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define re register
#define gc getchar
#define pc putchar
#define cs const

inline
ll getint(){
	re ll num;
	re char c;
	while(!isdigit(c=gc()));num=c^48;
	while(isdigit(c=gc()))num=(num<<1)+(num<<3)+(c^48);
	return num;
}

inline
void outint(ll a){
	static char ch[23];
	if(a==0)pc('0');
	while(a)ch[++ch[0]]=a-a/10*10,a/=10;
	while(ch[0])pc(ch[ch[0]--]^48);
}

cs int N=200015,logN=20;
int n,m;
int last[N],nxt[N<<1],to[N<<1],ecnt;
inline
void addedge(int u,int v){
	nxt[++ecnt]=last[u],last[u]=ecnt,to[ecnt]=v;
	nxt[++ecnt]=last[v],last[v]=ecnt,to[ecnt]=u;
}

int fa[N],top[N],siz[N],dep[N],son[N];
int in[N],out[N],tot;
inline
void dfs1(int u){
	siz[u]=1;
	in[u]=++tot;
	for(int re e=last[u],v=to[e];e;v=to[e=nxt[e]]){
		if(v==fa[u])continue;
		fa[v]=u;
		dep[v]=dep[u]+1;
		dfs1(v);
		siz[u]+=siz[v];
		if(siz[v]>siz[son[u]])son[u]=v;
	}
	out[u]=tot;
}

inline
void dfs2(int u){
	if(son[u]){
		top[son[u]]=top[u];
		dfs2(son[u]);
	}
	for(int re e=last[u],v=to[e];e;v=to[e=nxt[e]]){
		if(v==fa[u]||v==son[u])continue;
		top[v]=v;
		dfs2(v);
	}
}

inline
void tree_dissection(){
	dfs1(1);
	top[1]=1;
	dfs2(1);
}

inline
int LCA(int u,int v){
	while(top[u]!=top[v]){
		if(dep[top[v]]>dep[top[u]])swap(u,v);
		u=fa[top[u]];
	}
	if(dep[u]<dep[v])return u;
	else return v;
}

#define lowbit(x) (x&(-x))
int t1[N];
int t2[N];

inline void update(int *tr,int x,int val){
	for(;x<=n;x+=lowbit(x))tr[x]+=val;
}
inline int query(int *tr,int x){
	int res=0;
	for(;x;x-=lowbit(x))res+=tr[x];
	return res;
}

int cnt[N];
inline
int querypath(int u,int v){
	int lca=LCA(u,v),res=0;
	res+=query(t1,out[lca])-query(t1,in[lca]-1);
	res+=query(t2,in[u])+query(t2,in[v])-(query(t2,in[lca])<<1);
	res+=cnt[lca];
	return res;
}

inline
void updatepath(int u,int v){
	int lca=LCA(u,v);
	update(t1,in[u],1);
	update(t1,in[v],1);
	update(t1,in[lca],-2);
	update(t2,in[lca],1);
	update(t2,out[lca]+1,-1);
	++cnt[lca]; 
}

signed main(){
	int size=128<<20;
   // __asm__ ("movl  %0, %%esp\n"::"r"((char*)malloc(size)+size));//调试用这个 
    __asm__ ("movq %0,%%rsp\n"::"r"((char*)malloc(size)+size));
	n=getint();
	for(int re i=1;i<n;++i){
		int u=getint(),v=getint();
		addedge(u,v);
	}
	tree_dissection();
	m=getint();
	while(m--){
		int u=getint(),v=getint();
		cout<<(querypath(u,v)),pc('\n');
		updatepath(u,v);
	}
	exit(0);
}

猜你喜欢

转载自blog.csdn.net/zxyoi_dreamer/article/details/82946160
今日推荐