Luogu_P6374 树上询问

本文详细介绍了如何处理树上的询问问题,特别是Luogu_P6374题目的解法。通过建立无根树,并利用 Lowest Common Ancestor (LCA) 函数,针对每个询问(a, b, c),判断c是否在a和b的路径上,从而计算满足条件的节点i的数量。文章探讨了不同情况的解决方案,包括LCA性质的运用和不同情况下的节点计数策略。" 109498585,8162093,时间显式与时间隐式计算程序解析,"['数值计算', '科学计算', '传热学', '编程', '物理模型']

树上询问

链接

Luogu_P6374 树上询问

题目描述

给定一棵 n n n个点的无根树,有 q q q次询问。

每次询问给一个参数三元组 ( a , b , c ) (a,b,c) (a,b,c),求有多少个 i i i满足这棵树在以 i i i为根的情况下 a a a b b b L C A LCA LCA c c c

输入格式

第一行 2 2 2个数,为 n n n q q q

接下来 n − 1 n−1 n1行,每行 2 2 2个数,表示树的一条边。

接下来 q q q行,每行 3 3 3个数,为 ( a , b , c ) (a,b,c) (a,b,c)

输出格式

q q q行,每行一个数,为对于每个三元组的 i i i的个数。

输入输出样例

输入 #1
10 5
1 2
1 3
2 4
2 5
2 10
5 6
3 7
7 8
7 9
4 6 2
4 10 1
6 8 3
9 10 2
4 10 5
输出 #1
7
0
1
4
0
输入 #2
5 3
1 3
1 5
3 4
3 2
5 2 3
5 2 1
2 4 5
输出 #2
2
1
0
输入 #3
20 10
1 2
1 3
1 4
2 5
2 6
3 10
4 13
4 14
6 7
6 8
10 11
4 15
4 16
8 9
11 12
16 17
16 18
16 19
17 20
15 19 16
1 12 1
20 20 20
7 7 8
1 8 3
5 20 2
2 9 6
9 12 1
9 12 2
9 12 3
输出 #3
4
16
20
0
0
5
2
10
2
1

数据范围
本题按子任务测试:
s u b t a s k 1 ( 20 p t s ) s u b t a s k 1 ( 20 p t s ) subtask1 (20pts)subtask1(20pts) subtask1(20pts)subtask1(20pts) 1 ≤ n ≤ 1000 1 \leq n \leq1000 1n1000 1 ≤ q ≤ 1 ≤ q ≤ 500500 1 \leq q \leq1≤q≤ 500500 1q1q500500
s u b t a s k 2 ( 15 p t s ) s u b t a s k 2 ( 15 p t s ) subtask2 (15pts)subtask2(15pts) subtask2(15pts)subtask2(15pts) 1 ≤ n ≤ 1 0 5 1 \leq n \leq10^{5} 1n105 1 ≤ q ≤ 1 0 5 1 \leq q \leq10^{5} 1q105,树退化成链 。
s u b t a s k 3 ( 25 p t s ) s u b t a s k 3 ( 25 p t s ) subtask3 (25pts)subtask3(25pts) subtask3(25pts)subtask3(25pts) 1 ≤ n ≤ 5 × 1 0 5 1 \leq n \leq5 \times 10^{5} 1n5×105 1 ≤ q ≤ 1 0 5 1 \leq q \leq10^{5} 1q105,数据不随机 。
s u b t a s k 4 ( 40 p t s ) s u b t a s k 4 ( 40 p t s ) subtask4 (40pts)subtask4(40pts) subtask4(40pts)subtask4(40pts) 1 ≤ n ≤ 5 × 1 0 5 1 \leq n \leq5 \times 10^{5} 1n5×105 1 ≤ q ≤ 2 × 1 0 5 1 \leq q \leq 2 \times10^{5} 1q2×105

对于所有数据: 1 ≤ n ≤ 5 × 1 0 5 1 \leq n \leq5 \times 10^{5} 1n5×105 1 ≤ q ≤ 2 × 1 0 5 1 \leq q \leq2 \times 10^{5} 1q2×105

注:数据强度不高,不必卡常与快读快输。

思路

这是大家讨论出来的思路。

对于树上的任意节点 a a a b b b,连通它们的路径有且仅有一条。由于题目给出的是一棵无根树,所以连通 a a a b b b的路径上的任意一点(包括 a a a b b b)都有可能是 L C A ( a , b ) LCA(a,b) LCA(a,b)的值。

很显然,节点 L C A ( a , b ) LCA(a,b) LCA(a,b)拥有的包含 a a a b b b节点的子树上的节点,除了在连通 a a a b b b的路径上的节点以外,都不可能作为根,否则 L C A ( a , b ) LCA(a,b) LCA(a,b)的值将永远会是 a a a b b b,不会是路径上的其他点。

测试数据#2图

如图,连通节点 1 1 1 2 2 2的路径为 1 − 3 − 2 1-3-2 132,则 L C A ( 1 , 2 ) LCA(1,2) LCA(1,2)的值可能为 1 1 1 2 2 2 3 3 3。由图可得,成为根节点后能够使 L C A ( 1 , 2 ) LCA(1,2) LCA(1,2)的值为 1 1 1 2 2 2 3 3 3的节点有 1 1 1 2 2 2 3 3 3 4 4 4,并且不可能是 5 5 5,因为如果以 5 5 5为根节点的话, L C A ( 1 , 2 ) LCA(1,2) LCA(1,2)的值将为 1 1 1,不可能为 2 2 2 3 3 3

所以,我们要做的就是对给出的每个询问,判断节点 c c c是否在连通节点 a a a b b b的路径上。若不在,则答案为 0 0 0;若在,则答案为除去包含节点 a a a和包含节点 b b b的节点 c c c的子树后剩余节点的数目。

语言表述的方法有了,但怎么用计算机实现呢?

或许,我们可以像语言表述的那样,对于每个输入的 ( a , b , c ) (a,b,c) (a,b,c),找出连通 a a a b b b的路径,判断 c c c是否在这条路径上,再根据判断的结果求出答案。

不过,看看数据规模: 5 × 1 0 5 × 2 × 1 0 5 = 1 0 11 5 \times 10^{5} \times 2 \times 10^{5} =10^{11} 5×105×2×105=1011。很显然是不可能的。

这个时候,我们的 L C A LCA LCA函数就发挥作用了。

首先,我们先任意设置一个节点为根节点,并按照它制作一份 S T ST ST表,同时记录每个节点的深度。

接着,我们对节点 a a a b b b c c c L C A LCA LCA函数的运算。如果节点 c c c在连通 a a a b b b的路径上,那么它们一定满足一个性质,即: l c a ( l , l c a ( a , b ) ) = l c a ( a , b ) lca(l,lca(a,b))=lca(a,b) lca(l,lca(a,b))=lca(a,b),证明请自己推。

然后,我们需要分三种情况讨论。

  1. 第一种, l c a ( a , b ) = c lca(a,b)=c lca(a,b)=c,答案等于总的节点数减去包含包含节点 a a a b b b的节点 c c c的子树;
  2. 第二种, l c a ( a , c ) = c lca(a,c)=c lca(a,c)=c,答案等于节点 c c c及其子树的节点总数减去包含包含节点 a a a的节点 c c c的子树(不用做有关节点 b b b及其子树的运算是因为节点 c c c及其子树的节点总数与节点 b b b及其子树的节点总数互不影响);
  3. 第二种, l c a ( b , c ) = c lca(b,c)=c lca(b,c)=c,答案等于节点 c c c及其子树的节点总数减去包含包含节点 b b b的节点 c c c的子树;

这样,整个程序就完成了。

代码

#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
const int N=5*100000;
struct str{
	int to,nex;
}e[2*N+1];
int ls[2*N+1],cnt;
int dep[2*N+1],f[2*N+1][21],tot[N+1];
void add(int u,int v)
{
	cnt++;
	e[cnt].to=v;
	e[cnt].nex=ls[u];
	ls[u]=cnt;
}
void bfs(int root)//深度预处理 
{
	int fa,so;
	queue <int> q;
	dep[root]=1;
	q.push(root);
	while(!q.empty())
	{
		fa=q.front();
		q.pop();
		for(int i=ls[fa];i;i=e[i].nex)
		{
			so=e[i].to;
			if(!dep[so])
			{
				f[so][0]=fa;
				dep[so]=dep[fa]+1;
				q.push(so);
			}
		}
	}
}
void ST(int n)//制作树的ST表 
{
	for(int j=1;j<=20;j++)
		for(int i=1;i<=n;i++)
			f[i][j]=f[f[i][j-1]][j-1];
}
int lca(int x,int y)//计算两个点的LCA值 
{
	if(dep[x]>dep[y])
		swap(x,y);
	int d,k,t;
	d=dep[y]-dep[x];
	k=20;
	t=1<<k;
	while(d)
	{
		if(d>=t)
		{
			y=f[y][k];
			d-=t;
		};
		t/=2;
		k--;
	}
	if(x==y)
		return x;
	k=20;
	while(k>=0)
	{
		if(f[x][k]!=f[y][k])
		{
			x=f[x][k];
			y=f[y][k];
		}
		k--;
	}
	return f[x][0];
}
int dfs(int k)//计算每个节点子树上的总的节点数目(包括这个节点本身) 
{
	int sum=1;
	for(int i=ls[k];i;i=e[i].nex)
		if(f[k][0]!=e[i].to)
		{
			tot[e[i].to]=dfs(e[i].to);
			sum+=tot[e[i].to];
		}
	return sum;
}
int val(int so,int fa)//计算a或b所在的子树的节点数目 
{
	int k;
	if(so==fa)
		return 0;
	k=20;
	while(k>=0)
	{
		if(dep[f[so][k]]>dep[fa]) 
			so=f[so][k];
		k--;
	}
	return tot[so];
}
bool check(int a,int b,int c)//判断c是否在连通a、b的路径上 
{
	int l=lca(a,b);
	return lca(l,c)==l;
}
int main()
{
	int n,q,x,y,v,a,b,c,ans;
	scanf("%d%d",&n,&q);
	for(int i=1;i<=n-1;i++)
	{
		scanf("%d%d",&x,&y);
		add(x,y);
		add(y,x);
	}
	bfs(1);
	ST(n);
	tot[1]=dfs(1);
	for(int i=1;i<=q;i++)
	{
		ans=0;
		scanf("%d%d%d",&a,&b,&c);
		if(check(a,b,c)&&(lca(a,b)==c))//分类讨论情况1
			ans=n-val(a,c)-val(b,c);
		else if(check(a,b,c)&&(lca(a,c)==c))//分类讨论情况2
			ans=tot[c]-val(a,c);
		else if(check(a,b,c)&&(lca(b,c)==c))//分类讨论情况3
			ans=tot[c]-val(b,c);
		printf("%d\n",ans);
	}
	return 0;
}
下载方式:https://pan.quark.cn/s/a4b39357ea24 布线问题(分支限界算法)是计算机科学和电子工程领域中一个广为人知的议题,它主要探讨如何在印刷电路板上定位两个节点间最短的连接路径。 在这一议题中,电路板被构建为一个包含 n×m 个方格的矩阵,每个方格能够被界定为可通行或不可通行,其核心任务是定位从初始点到最终点的最短路径。 分支限界算法是处理布线问题的一种常用策略。 该算法与回溯法有相似之处,但存在差异,分支限界法仅需获取满足约束条件的一个最优路径,并按照广度优先或最小成本优先的原则来探索解空间树。 树 T 被构建为子集树或排列树,在探索过程中,每个节点仅被赋予一次成为扩展节点的机会,且会一次性生成其全部子节点。 针对布线问题的解决,队列式分支限界法可以被采用。 从起始位置 a 出发,将其设定为首个扩展节点,并将与该扩展节点相邻且可通行的方格加入至活跃节点队列中,将这些方格标记为 1,即从起始方格 a 到这些方格的距离为 1。 随后,从活跃节点队列中提取队首节点作为下一个扩展节点,并将与当前扩展节点相邻且未标记的方格标记为 2,随后将这些方格存入活跃节点队列。 这一过程将持续进行,直至算法探测到目标方格 b 或活跃节点队列为空。 在实现上述算法时,必须定义一个类 Position 来表征电路板上方格的位置,其成员 row 和 col 分别指示方格所在的行和列。 在方格位置上,布线能够沿右、下、左、上四个方向展开。 这四个方向的移动分别被记为 0、1、2、3。 下述表格中,offset[i].row 和 offset[i].col(i=0,1,2,3)分别提供了沿这四个方向前进 1 步相对于当前方格的相对位移。 在 Java 编程语言中,可以使用二维数组...
源码来自:https://pan.quark.cn/s/a4b39357ea24 在VC++开发过程中,对话框(CDialog)作为典型的用户界面组件,承担着与用户进行信息交互的重要角色。 在VS2008SP1的开发环境中,常常需要满足为对话框配置个性化背景图片的需求,以此来优化用户的操作体验。 本案例将系统性地阐述在CDialog框架下如何达成这一功能。 首先,需要在资源设计工具中构建一个新的对话框资源。 具体操作是在Visual Studio平台中,进入资源视图(Resource View)界面,定位到对话框(Dialog)分支,通过右键选择“插入对话框”(Insert Dialog)选项。 完成对话框内控件的布局设计后,对对话框资源进行保存。 随后,将着手进行背景图片的载入工作。 通常有两种主要的技术路径:1. **运用位图控件(CStatic)**:在对话框界面中嵌入一个CStatic控件,并将其属性设置为BST_OWNERDRAW,从而具备自主控制绘制过程的权限。 在对话框的类定义中,需要重写OnPaint()函数,负责调用图片资源并借助CDC对象将其渲染到对话框表面。 此外,必须合理处理WM_CTLCOLORSTATIC消息,确保背景图片的展示不会受到其他界面元素的干扰。 ```cppvoid CMyDialog::OnPaint(){ CPaintDC dc(this); // 生成设备上下文对象 CBitmap bitmap; bitmap.LoadBitmap(IDC_BITMAP_BACKGROUND); // 获取背景图片资源 CDC memDC; memDC.CreateCompatibleDC(&dc); CBitmap* pOldBitmap = m...
<think>题目要求:给定一棵树,多次询问每次询问给定三个点x,y,z,求一个点p,使得p到x,y,z的距离和最小,并输出该点和最小距离和。结论:1.集合点p一定是三个点两两的LCA(最近公共祖先)中的一个,且是深度最大的那个LCA。2.最小距离和=dep[x]+dep[y]+dep[z]-dep[lca(x,y)]-dep[lca(x,z)]-dep[lca(y,z)]因此,我们需要实现:-建树-预处理深度和倍增数组(用于求LCA)-对于每个询问,计算三个LCA:lca(x,y),lca(x,z),lca(y,z),然后取深度最大的那个作为p-然后按照上述公式计算距离和注意:距离和的计算公式推导:设p为所求点,则距离和为:dep[x]+dep[p]-2*dep[lca(x,p)]+同理y,z。但是这样不好直接计算。利用结论:点p是三个LCA中深度最大的那个,记为D。则距离和=(dep[x]+dep[y]+dep[z])-(dep[lca(x,y)]+dep[lca(x,z)]+dep[lca(y,z)])为什么?因为:考虑三个点两两的路径,它们会经过三个LCA,而整个路径被重复计算的部分就是这三个LCA的深度,且由于p是三个LCA中最深的,所以路径在p处汇聚。具体步骤:1.预处理:使用BFS或DFS求每个节点的深度和倍增数组(祖先数组fa[][])2.实现LCA函数:先调整深度,然后一起往上跳。3.处理每个询问。注意:本题数据范围n,m<=500000,因此使用倍增LCA时,注意数组大小和常数优化(使用scanf/printf)。代码实现如下:</think>### 问题解答 题目要求在一棵树中为三个点找到一个集合点,使得到这三个点的距离之和最小。以下是完整的C++解决方案: ```cpp #include <iostream> #include <cstdio> #include <vector> #include <queue> #include <algorithm> using namespace std; const int MAXN = 500005; const int LOG = 20; vector<int> graph[MAXN]; int depth[MAXN]; int parent[MAXN][LOG]; int n, m; void initLCA() { queue<int> q; q.push(1); depth[1] = 1; while (!q.empty()) { int u = q.front(); q.pop(); for (int v : graph[u]) { if (v == parent[u][0]) continue; depth[v] = depth[u] + 1; parent[v][0] = u; for (int j = 1; j < LOG; j++) { parent[v][j] = parent[parent[v][j-1]][j-1]; } q.push(v); } } } int lca(int a, int b) { if (depth[a] < depth[b]) swap(a, b); for (int i = LOG-1; i >= 0; i--) { if (depth[parent[a][i]] >= depth[b]) { a = parent[a][i]; } } if (a == b) return a; for (int i = LOG-1; i >= 0; i--) { if (parent[a][i] != parent[b][i]) { a = parent[a][i]; b = parent[b][i]; } } return parent[a][0]; } int main() { scanf("%d %d", &n, &m); for (int i = 1; i < n; i++) { int u, v; scanf("%d %d", &u, &v); graph[u].push_back(v); graph[v].push_back(u); } initLCA(); while (m--) { int x, y, z; scanf("%d %d %d", &x, &y, &z); int l1 = lca(x, y); int l2 = lca(x, z); int l3 = lca(y, z); int p = l1; if (depth[l2] > depth[p]) p = l2; if (depth[l3] > depth[p]) p = l3; int dist = depth[x] + depth[y] + depth[z] - depth[l1] - depth[l2] - depth[l3]; printf("%d %d\n", p, dist); } return 0; } ``` ### 算法解释 1. **LCA预处理**: - 使用BFS遍历树并计算每个节点的深度 - 构建倍增数组`parent[][]`,其中`parent[i][j]`表示节点i的2^j级祖先 - 时间复杂度:O(n log n) 2. **LCA查询**: - 调整较深节点到相同深度 - 同时向上跳跃寻找最近公共祖先 - 时间复杂度:O(log n) 每次查询 3. **最优集合点选择**: - 计算三对点的LCA:`lca(x,y)`, `lca(x,z)`, `lca(y,z)` - 选择深度最大的LCA作为集合点p - 距离和公式:`dist = depth[x]+depth[y]+depth[z]-depth[l1]-depth[l2]-depth[l3]` 4. **数学证明**: - 三个点路径的交点必然在深度最大的LCA处 - 距离和公式本质是计算三条路径的总长度减去重叠部分 - 该点能最小化到三个点的路径总和 5. **复杂度分析**: - 预处理:O(n log n) - 每次查询:O(log n) - 总复杂度:O((n+m) log n),满足500000数据规模要求 ### 相关问题
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值