【lca,树上差分】P3128 [USACO15DEC] Max Flow P

题意

给定大小为 n ( 2 ≤ n ≤ 5 × 1 0 4 ) n(2 \leq n \leq 5 \times 10^4) n(2n5×104) 的树,并给定 m ( 1 ≤ m ≤ 1 0 5 ) m(1 \leq m \leq 10^5) m(1m105) 条树上的路径(给定两个端点,容易证明两个点树上路径唯一),求上述路径中最多有几条路径同时经过一个点。

【样例输入】

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

【样例输出】
9
在这里插入图片描述

思路

在这里插入图片描述

考虑树上差分。树上差分,顾名思义,就是在树上进行类似差分的操作。

假设有一条 6 ← 4 6 \gets 4 64 的路径。

如果要暴力标记,则单次复杂度最坏情况下近似于 O ( n ) O(n) O(n),时间爆炸。

但是,如果我们把原路径看作 6 ← 1 + 1 ← 5 6 \gets 1 + 1 \gets 5 61+15 两条路径的和的话,我们就会有一些发现:

对于(更改后)路径上每一个点,我们只需要标记头尾,就可以在树上形成类似差分的东西。

考虑如何差分,记差分数组为 c h a f e n chafen chafen c h a f e n i chafen_i chafeni 表示第 i i i 个点及其所有祖先的路径数量之和中经过节点 i i i 的那一部分,要标记一条由 i i i j j j 的路径。

c h a f e n x ← c h a f e n x + 1 , c h a f e n y ← c h a f e n y + 1 , c h a f e n l c a ( x , y ) ← c h a f e n l c a ( x , y ) − 1 , c h a f e n f a l c a ( x , y ) ← c h a f e n f a l c a ( x , y ) − 1 chafen_x \gets chafen_x + 1,chafen_y \gets chafen_y + 1,chafen_{lca(x,y)} \gets chafen_{lca(x,y)} - 1,chafen_{fa_{lca(x,y)}} \gets chafen_{fa_{lca(x,y)}} - 1 chafenxchafenx+1,chafenychafeny+1,chafenlca(x,y)chafenlca(x,y)1,chafenfalca(x,y)chafenfalca(x,y)1

拿上图中 6 ← 4 6 \gets 4 64 举例,可以发现 l c a ( 4 , 6 ) = 1 lca(4,6) = 1 lca(4,6)=1

我们标记 c h a f e n 6 , c h a f e n 4 chafen_6,chafen_4 chafen6,chafen4 加上 1 1 1,可以发现 c h a f e n 1 chafen_1 chafen1 作为两者共同祖先,加了两次,故要扣掉。

因为两个的路径不包括他们最近共同祖先的祖先,所以 c h a f e n f a l c a ( x , y ) chafen_{fa_{lca(x,y)}} chafenfalca(x,y) 也要减一。

代码

#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,m;
int head[50005],nex[100005],to[100005],cnt = 0;
int deep[50005],max_deep;
int d[50005][25];

int chafen[50005];
void add(int x,int y) {
	nex[++cnt] = head[x];
	head[x] = cnt;
	to[cnt] = y;
}
void find_deep(int now,int fa) {
	if(now != fa)d[now][0] = fa;
	for(int i = head[now];i;i = nex[i]) {
		if(to[i] != fa) {
			deep[to[i]] = deep[now] + 1;
			find_deep(to[i],now); 
		}
	}
	max_deep = max(max_deep,deep[now]);
	return;
}
void jump1(int &x,int &y) {
	int del_deep = deep[x] - deep[y];
	int r = 0;
	while(del_deep) {
		if(del_deep & 1) {
			x = d[x][r];
		}
		r++;
		del_deep >>= 1;
	}
	return;
}
int jump(int x,int y) {
	int r = 0;
	while((1 << r) <= deep[x]) r++;
	r--;
	for(;r >= 0;r--) {
		if(d[x][r] != d[y][r]) {
			x = d[x][r];
			y = d[y][r];
		}
	}
	if(x == y) return x;
	else return d[x][0];
}
int ans = -1e18;
int lca(int x,int y) {
	if(deep[y] > deep[x]) swap(x,y);
	if(deep[x] != deep[y]) jump1(x,y);
	if(x == y) return x;
	return jump(x,y);
}
void dfs(int now,int fa) {
	for(int i = head[now];i;i = nex[i]) {
		if(to[i] != fa) dfs(to[i],now),chafen[now] += chafen[to[i]];
	}
	ans = max(ans,chafen[now]);
	//printf("%lld %lld\n",now,chafen[now]);
	return;
}
signed main() {
	scanf("%lld %lld",&n,&m);
	for(int i = 1;i < n;i++) {
		int x,y;
		scanf("%lld %lld",&x,&y);
		add(x,y),add(y,x);
	} 
	find_deep(1,1);
	for(int i = 1;(1 << i) <= max_deep;i++) {
		for(int j = 1;j <= n;j++) {
			if(deep[j] < (1 << i)) continue;
			d[j][i] = d[d[j][i - 1]][i - 1];
		}
	}
	for(int i = 1;i <= m;i++) {
		int x,y;
		scanf("%lld %lld",&x,&y);
		int z = lca(x,y);
		chafen[x]++,chafen[y]++,chafen[z]--,chafen[d[z][0]]--;
	}
	dfs(1,1);
	printf("%lld\n",ans);
	return 0;
}

AC记录

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值