【题解】LuoGuU67748:坤链剖分

原题传送门

题目大意:一颗n个节点的树,m个询问(u,v),每次输出经过了u到v的最短路径的坤链的条数。

首先可以发现的是每个叶子借点都对应着一条坤链。

30%

暴力,对于每一个询问,把u到v的路径上的边打标记,从根节点开始遍历每一条坤链,遍历到叶子节点结束,到叶子结点结束时统计该坤链是否经过了u到v的路径,若是,则统计入答案。

20%
链的情况,送分,发现整棵树只有一个叶子节点,所有只有一条坤链,且该链一定经过任意一条路径,所以对于每个询问,都只要输出1即可。

20%
每个询问的两个端点的lca是根节点的情况。
如果思考这个子任务的话会发现这其实是满分做法的简化版。
可以发现一个有用的性质:边是有深度的(根结点为1),若一条坤链经过一条边,那么该坤链必定经过这条边上面那条边。
举个例子:有两条边1->2,2->3,1为根节点,若一条坤链经过2->3,则该链必定经过1->2
那么,既然两点的lca是1,是不是只要看看有多少坤链经过1连到u,v的第一条边
这个东西可以dfs一遍解决

100%
上一部分的加强版。
首先记录经过每条边的坤链的条数,记录为点的权值,用dfs解决。这里以点代边。
然后处理询问,倍增跳lca,找到lca下两点,那两点的权值之和即为答案。
若两点的lca为其中一点,则只需要lca下一点的权值作为答案。

然后对于如何记录有多少条坤链经过某条边,就是计算这个点的子树中有几个叶子
可以用差分思想,这是我一年半前用的做法
也可以直接统计
Code:

#include <bits/stdc++.h>
#define maxn 1000010
using namespace std;
struct Edge{
	int to, next;
}edge[maxn << 1];
int num, head[maxn], n, m, d[maxn], fa[maxn][25], val[maxn];

inline int read(){
	int s = 0, w = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') w = -1;
	for (; isdigit(c); c = getchar()) s = (s << 1) + (s << 3) + (c ^ 48);
	return s * w;
}

void addedge(int x, int y){ edge[++num] = (Edge){y, head[x]}, head[x] = num; }

void dfs(int u, int pre){
	d[u] = d[pre] + 1, fa[u][0] = pre;
	val[u] = 1;
	for (int i = 0; fa[u][i]; ++i) fa[u][i + 1] = fa[fa[u][i]][i];
	for (int i = head[u]; i; i = edge[i].next){
		int v = edge[i].to;
		if (v == pre) continue;
		dfs(v, u);
		val[u] += val[v];
	}
	if (val[u] > 1) --val[u];
}

int lca(int u, int v){
	if (d[u] < d[v]) swap(u, v);
	for (int i = 20; i >= 0; --i) if (d[fa[u][i]] > d[v]) u = fa[u][i];
	if (fa[u][0] == v)  return val[u];
	if (d[u] > d[v]) u = fa[u][0];
	for (int i = 20; i >= 0; --i) if (fa[u][i] != fa[v][i]) u = fa[u][i], v = fa[v][i];
	return val[u] + val[v];
}

int main(){
	n = read(), m = read();
	for (int i = 1; i < n; ++i){
		int x = read(), y = read();
		addedge(x, y), addedge(y, x);
	}
	dfs(1, 0);
//	for (int i = 1; i <= n; ++i) printf("val[%d] = %d\n", i, val[i]);
	while (m--){
		int x = read(), y = read();
		printf("%d\n", lca(x, y));
	}
	return 0;
}
#include <bits/stdc++.h>
#define maxn 1000010
using namespace std;
struct Edge{
	int to, next;
}edge[maxn << 1];
int head[maxn], num, d[maxn], fa[maxn][30], tot[maxn], cnt, n, m;

inline int read(){
	int s = 0, w = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') w = -1;
	for (; isdigit(c); c = getchar()) s = (s << 1) + (s << 3) + (c ^ 48);
	return s * w;
}

void add_edge(int x, int y){ edge[++num].to = y; edge[num].next = head[x]; head[x] = num; }

void dfs(int u, int pre){
	d[u] = d[pre] + 1; fa[u][0] = pre;
	for (int i = 0; fa[u][i]; ++i) fa[u][i + 1] = fa[fa[u][i]][i];
	int tmp = cnt, flag = 0;
	for (int i = head[u]; i; i = edge[i].next){
		int v = edge[i].to;
		if (v != pre){
			flag = 1; dfs(v, u);
		}
	}
	cnt += flag == 0;
	tot[u] = cnt - tmp;
}

int query(int u, int v){
	if (d[u] < d[v]) swap(u, v);
	for (int i = 20; i >= 0; --i) if (d[v] + (1 << i) < d[u]) u = fa[u][i];
	if (fa[u][0] == v) return tot[u];
	if (d[u] > d[v]) u = fa[u][0];
	for (int i = 20; i >= 0; --i) if (fa[u][i] != fa[v][i]) u = fa[u][i], v = fa[v][i];
	return tot[u] + tot[v];
}

int main(){
	n = read(), m = read();
	for (int i = 1; i < n; ++i){
		int x = read(), y = read();
		add_edge(x, y); add_edge(y, x);
	}
	dfs(1, 0);
	while (m--){
		int x = read(), y = read();
		printf("%d\n", query(x, y));
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值