P8805 [蓝桥杯 2022 国 B] 机房

发布于:2024-05-16 ⋅ 阅读:(153) ⋅ 点赞:(0)

P8805 [蓝桥杯 2022 国 B] 机房

分析

是一道lca题目,可以直接套模板 + 前缀和处理点权

具体思路:

1.n台电脑用n-1条网线相连,任意两个节点之间有且仅有一条路径(拆分成各自到公共祖先节点的路径——lca);

2.“延迟时间”:看成是节点点权(要求一条链上某个区间的值——前缀和)

——用sum[]存储根节点到 i 节点的点权和

LCA

三种求法:

(1)朴素求法:

先将两点位于同一深度,然后同时向上跳;一次查询:O(n^2)

(2)倍增求法:

朴素求法的改进版(朴素是一步一步跳,倍增是用f[][]优化,每次向上都是以其父节点为目标)

(3)Tarjan(留一下,还没学~)

倍增求法的步骤:

1.存图/树

2.dfs(int x,int father)更新f[][](表示节点 i 的第2^j个祖先节点)、d[](深度)

(1)更新d[x]和f[x][0]

(2)for(1~20)更新f[x][i]

(3)for(遍历邻接点)递归dfs()

3.lca

(1)先将两点跳到同一深度(深度大的往上跳)

(2)若相等,则该点就是lca;若不相等,则从大到小,如果f[][]不等,则更新两点,lca=f[x][0]

代码 

#include<iostream>
#include<cstring>
using namespace std;

const int N = 100010;
int head[N],d[N],f[N][30],a[N],sum[N],cnt,n,m;
struct edges{
	int to;
	int ne;
}e[N*2];//存两条边记得数组开成2倍

//初始化别忘了(容易忘建议直接写main中)
void init()
{
	memset(head,-1,sizeof head);
}
//链式前向星存边
void addedge(int u,int v)
{
	e[cnt].to = v;
	e[cnt].ne = head[u];
	head[u] = cnt ++;
}
//dfs处理d[]、f[][]、sum[]
void dfs(int x,int father)
{
	d[x] = d[father] + 1;
	f[x][0] = father;
	sum[x] = sum[f[x][0]] + a[x];  //lca模板多了这一步~
	for(int i = 1;i <= 20;i ++)
	{
		f[x][i] = f[f[x][i-1]][i-1];
	}
	for(int i = head[x];i != -1;i = e[i].ne)
	{
		int j = e[i].to;
		if(j == father) continue; //加的是双向边,所以邻接点有其父节点,遍历时跳过
		dfs(j,x);	//递归遍历
	}
	return ;
}
//lca找到最近公共祖先节点
int lca(int u,int v)
{
	if(d[u] < d[v]) swap(u,v);
		for(int i = 20;i >= 0;i --)
		{
			if(d[f[u][i]] >= d[v]) u = f[u][i];
		}
		if(u == v)
		{
			return u;
		}
		for(int i = 20;i >= 0;i --)
		{
			if(f[u][i] != f[v][i])
			{
				u = f[u][i];
				v = f[v][i];
			}
		}
		return f[u][0];
}

int main()
{
	init();
	scanf("%d %d",&n,&m);
	for(int i = 0;i < n - 1;i ++)
	{
		int u,v;
		scanf("%d %d",&u,&v);
		a[u] ++,a[v] ++;
		addedge(u,v),addedge(v,u);
	}
	dfs(1,0);
	while(m --)
	{
		int u,v;
		scanf("%d %d",&u,&v);
		int mid = lca(u,v);
		int ans = sum[u] + sum[v] - 2 * sum[mid] + a[mid];  //两点之间的权值和
		printf("%d\n",ans);
	}
	return 0;
}