首页 > 其他分享 >AcWing 1078. 旅游规划 (DFS找树的直径+直径中点性质求解,无DP)

AcWing 1078. 旅游规划 (DFS找树的直径+直径中点性质求解,无DP)

时间:2024-08-21 19:15:59浏览次数:3  
标签:diam 1078 int 找树 DFS delta 直径 中点

原题链接

题目描述

image


算法

引用自 树的直径 - OI-Wiki

若树上所有边边权均为正,则树的所有直径中点重合
证明:使用反证法。设两条中点不重合的直径分别为 \(\delta(s,t) 与 \delta(s',t')\),中点分别为 \(x\) 与 \(x'\)。显然,\(\delta(s,x) = \delta(x,t) = \delta(s',x') = \delta(x',t')\)。

无负权边的树所有直径的中点重合

有 \(\delta(s,t') = \delta(s,x) + \delta(x,x') + \delta(x',t') > \delta(s,x) + \delta(x,t) = \delta(s,t)\),与 \(\delta(s,t)\) 为树上任意两节点之间最长的简单路径矛盾,故性质得证。

补充说明:当树的直径长度为偶数的时候,所有直径必定经过两中点及其间的边,证明与上文类似。

根据以上内容,所有直径必过中点且一定以其为自己的中点,所以总体思路为找到中点后,从中点出发找到与中点距离为直径一半的点,即为直径一端点

所以,我们可以两次 DFS 找出树直径的两端点,然后再一次 DFS 找到树的直径的一个或两个中点(已知直径两端点之间路径上距离一端点 直径>>1(直径+1)>>1 的点,可能有两个中点),最后从一个或两个中点分别 DFS 找与中点距离为直径一半的点,找到后回溯时把路径上所有点都打上标记,最后输出有标记的点即可。

注意一个细节:当有两个中点时,为了避免多找或漏找,可以在将判断条件设置为 距离>=(直径+1)>>1,这样当直径为偶数时(此时中点只有一个)+1 不影响判断;当直径为奇数(此时有两个中点),向上取整可以使每一个中点都找到更靠近另一个中点的端点。否则如果不向上取整的话,可能找到在另一个中点那边距离自己 直径>>1 的点,但实际上直径应当是自己这边的一端距离自己 直径>>1,另一个中点的那一端距离自己 (直径+1)>>1(即距离另一个中点 直径>>1),所以会把不是端点的点判为端点,就 WA 了(被这个坑了好几次)。

时间复杂度

五次 DFS 均为 \(O(N)\),总时间复杂度为 \(O(N)\)

C++ 代码

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

const int N=2e5+5;
int n;

struct Allan{
	int to,nxt;
}edge[N<<1];
int head[N],idx;
inline void add(int x,int y)
{
	edge[++idx]={y,head[x]};
	head[x]=idx;
	return;
}

void DFS_in(int *len,int x,int fa=0)
{
	for(int i=head[x];i;i=edge[i].nxt)
	{
		int y=edge[i].to;
		if(y==fa) continue;
		len[y]=len[x]+1;
		DFS_in(len,y,x);
	}
	return;
}

int pos1,pos2,diam,core1,core2;
int len_core[N];
bool is_diam[N];
bool DFS_find_path(int x,int fa=0)
{
	if(len_core[x]>=(diam+1)>>1)
		return is_diam[x]=true;
	bool on_diam=false;
	for(int i=head[x];i;i=edge[i].nxt)
	{
		int y=edge[i].to;
		if(y==fa) continue;
		len_core[y]=len_core[x]+1;
		on_diam|=DFS_find_path(y,x);
	}
	is_diam[x]|=on_diam;
	return on_diam;
}

int len1[N],len2[N];
bool DFS_find_core(int x,int fa=0)
{
	if(x==pos2) return true;
	bool on_diam=false;
	for(int i=head[x];i;i=edge[i].nxt)
	{
		int y=edge[i].to;
		if(y==fa) continue;
		on_diam|=DFS_find_core(y,x);
		if(on_diam&&len2[x]==diam>>1) core1=x;
		if(on_diam&&len2[x]==(diam+1)>>1) core2=x;
	}
	return on_diam;
}

int main()
{
	scanf("%d",&n);
	for(int i=1;i<n;i++)
	{
		int x,y; scanf("%d%d",&x,&y);
		x++,y++;
		add(x,y),add(y,x);
	}
	DFS_in(len1,1);
	for(int i=1;i<=n;i++)
		if(len1[i]>len1[pos1]) pos1=i;
	DFS_in(len2,pos1);
	for(int i=1;i<=n;i++)
		if(len2[i]>len2[pos2]) pos2=i;
	diam=len2[pos2];
	DFS_find_core(pos1);
	DFS_find_path(core1);
	memset(len_core,0,sizeof(len_core));
	DFS_find_path(core2);
	for(int i=1;i<=n;i++)
		if(is_diam[i]) printf("%d\n",i-1);
	return 0;
}

标签:diam,1078,int,找树,DFS,delta,直径,中点
From: https://www.cnblogs.com/jerrycyx/p/18372359

相关文章

  • 『树的直径、重⼼』Day10
    DrazilandMorningExercise\(f\)可以换根求。对于一段乱序序列,你不好求其中max-min的限制。根据重心的性质,如果你让重心为root,那么向下\(f\)一定单减。这样,你就对每个点在末尾的情况,树上倍增找到最大的点,树上差分即可。现在想到了好像可以线段树合并,那么你当前点就......
  • 【题解】Solution Set - NOIP2024集训Day10 树的直径、重⼼、中⼼
    【题解】SolutionSet-NOIP2024集训Day10树的直径、重⼼、中⼼https://www.becoder.com.cn/contest/5464「CF516D」DrazilandMorningExercise首先,我们可以换根求出所有点的\(f\)。然后不会了……思考一下,一条直径提供的到底时什么。实际上,一条直径上的点取到\(f\)......
  • 题解:P10781 【MX-J1-T1】『FLA - III』Spectral
    P10781【MX-J1-T1】『FLA-III』Spectral题解(非正解,正解应该是数学题。)这道题很简单,分析题意就可以得出核心代码:for(inti=1;i<=n;i++){ans=k+ans/i;}那么恭喜你获得$40$pts。为什么呢?因为题目需要的是最高温度,而烧碳获得的温度可能小于烧炭时减低的温度。简单说......
  • 二叉查找树
    如果一棵二叉树能“查找”,那么这棵树的每一个节点都有一个“键值”,这些节点都按照键值有序排列,这棵树就叫做二叉查找树(BST)。BST的性质每个节点都有唯一的键值,而且可以比较大小。每个节点的左儿子的键值小于自己的键值,自己的键值又小于右儿子的键值,最小键值的节点没有左儿......
  • 【算法模板】计算几何:旋转卡壳求凸包直径
    旋转卡壳算法是一种几何算法,主要用于在二维平面上求解与凸包相关的最优问题。该算法利用凸包顶点的顺序性和对称性,通过模拟两个卡壳(calipers)沿着凸包边界的旋转来寻找最优解。常见的应用包括计算凸包的直径(即最远点对之间的距离)、最小包围矩形(最小面积矩形),以及最小宽度(宽度......
  • 编写一个程序,打开和读取一个文本文件,并统计文件中每个单词出现的次数。用改进的二叉查
    /编写一个程序,打开和读取一个文本文件,并统计文件中每个单词出现的次数。用改进的二叉查找树存储单词及其出现的次数。程序在读入文件后会提供一个有三个选项菜单。第一个选项是列出所有的单词和出现的次数。第二个选项是让用户输入一个单词,程序报告该单词在文件中出现的次数。......
  • 【树的直径 求树中距离跟阶段点最远的点】CodeForce1822F.md
    CF1822F-Problem-F-Codeforces题目大意:无根树的每条边为k,定义操作:移动根节点为把当前的根ROOT移动到相邻节点,每次代价为c,定义成本=从ROOT出发到达的最长的路径的长度,利润=成本-代价,求利润最大值\[\begin{align}&\huge\color{red}记得开\text{longlong}\\\\\\&\huge思......
  • 浅谈图论中树及其相关知识点(树的遍历、直径、最近公共祖先)(c++)
    目录前言一.关于树二.树的遍历(一)遍历方式常见遍历1.DFS遍历2.BFS遍历二叉树遍历1.先序遍历2.中序遍历3.后序遍历(二)例题讲解1.P1030[NOIP2001普及组]求先序排列思路AC代码 2.P5908猫猫和企鹅思路AC代码  3.P1395会议思路AC代码三.树的直径(一)定......
  • LeetCode513. 找树左下角的值
    题目链接:https://leetcode.cn/problems/find-bottom-left-tree-value/description/题目叙述:给定一个二叉树的根节点root,请找出该二叉树的最底层最左边节点的值。假设二叉树中至少有一个节点。示例1:输入:root=[2,1,3]输出:1示例2:输入:[1,2,3,4,null,5,6,n......
  • 题解:P10781 【MX-J1-T1】『FLA - III』Spectral
    本题的主要思路就是数学。首先,让我们先来打一个表。\(i\)\(1\)\(2\)\(3\)\(4\)\(\dots\)\(T_{i}\)\(k\)\(1.5k\)\(1.5k\)\(1.375k\)\(\dots\)易用肉眼看见,自\(T_{3}\)之后数越来越小,于是我们大胆猜测,若\(n\ne1\),则它的最大值是\(1.5k\)否则\(k\)。......