贤鱼的刷题日常(树的直径)-P3304 [SDOI2013] 直径

🏆今日学习目标:

🍀例题讲解P3304 [SDOI2013] 直径

✅创作者:贤鱼

⏰预计时间:25分钟

🎉个人主页:贤鱼的个人主页

🔥专栏系列:c++

🍁贤鱼的个人社区,欢迎你的加入 贤鱼摆烂团

P3304 [SDOI2013] 直径

🍁题目

[SDOI2013] 直径

题目描述

小Q最近学习了一些图论知识。根据课本,有如下定义。树:无回路且连通的无向图,每条边都有正整数的权值来表示其长度。如果一棵树有N个节点,可以证明其有且仅有N-1 条边。

路径:一棵树上,任意两个节点之间最多有一条简单路径。我们用 dis(a,b)表示点a和点b的路径上各边长度之和。称dis(a,b)为a、b两个节点间的距离。

直径:一棵树上,最长的路径为树的直径。树的直径可能不是唯一的。

现在小Q想知道,对于给定的一棵树,其直径的长度是多少,以及有多少条边满足所有的直径都经过该边。

输入格式

第一行包含一个整数N,表示节点数。 接下来N-1行,每行三个整数a, b, c ,表示点 a和点b之间有一条长度为c的无向边。

输出格式

共两行。第一行一个整数,表示直径的长度。第二行一个整数,表示被所有直径经过的边的数量。

样例 #1

样例输入 #1

6
3  1 1000
1  4 10
4  2 100
4  5 50
4  6 100

样例输出 #1

1110 
2

提示

【样例说明】

直径共有两条,3 到2的路径和3到6的路径。这两条直径都经过边(3, 1)和边(1, 4)。

对于100%的测试数据:2<=N<=200000,所有点的编号都在1...N的范围内,边的权值<=10^9。

🍁 思路

首先拆分问题

1判断直径长度

2直径所过共同边最长是多长

解释一下第二个问题,如样例所示,两条直径都经过了1-3,1-4;故长度为2

* 如何解决第一个问题呢?

  • 如何搜索呢?
  • 简单~
    一号点开始,用链式前向星寻找与其相连的点
    剪纸:记录当前点的父节点f[i],dfs(x,f[x]),我们只需要保证不要两点之间来回穿梭即可
    利用数组deep储存点1到每一个点的距离
    贤鱼贤鱼,按照样例,从1开始找并不能找到直径啊
    所以
    我们还要进行下一步操作

    通过寻找deep中的max,可以找到3这个点
    那么!!!
    我们就以3为原点,再次dfs

    这样,就解决了第一个问题
    如何解决第二个问题?

🍁AC代码

首先利用数组f,是不是储存了每一个点的父节点

我们在判断上一问寻找max的时候,储存一下max点的序号为c

创建数组isol,循环从c到他的终点,标记路线(为了求出直径上的每一个点并且标记)

利用二分思想,r和l

举个例子(这里假设边权都为1)

我们可以得到三条直径

并且对一条直径做了记录

很明显,答案为2

左端点为l,右端点为r,mid不是中点,是中间任意一个点,记录l-mid和r-mid长度

可以看到这里尿分叉了

在这里,记录点mid到任意一个非isol上的点 最远距离
如果和mid到l的距离,就将l挪到mid(只能挪动一次,下面解释)
如果和mid到r的距离相同,就移动r
为什么呢?

如果mid到非isol的最远点和到r/l距离相同,说明这也是一条直径,那么,这条直径往下的点,和isol上与他距离相同的那部分点,必然不经过同一条边
这俩就可以直接剪枝


最后寻找完毕,判断l-r之间多少条边,就是最终答案

解释一下为什么左边只缩一次

和刚才几乎没有变化对吧,答案还是2

但是

如果l缩小两次

答案多了一条啊

所以说,l如果缩小一次以上,就会将已经剪枝的部分给舍弃,当那条边不存在,可是那条边存在

所以只能缩小一次

cpp 复制代码
#include<cmath>
#include<iostream>
#include<cstring>
#include<iostream>
#define int long long
using namespace std;
int a,b,c,n;
int cc;
struct node{
	int u,v,w,next;
}e[2000005];
int head[2000005];
int cnt;
int dp[2000005];
int deep[2000005];
int f[2000005];
int isol[2000005];
int maxd=0;
void add(int u,int v,int w){
	e[++cnt].u=u;
	e[cnt].v=v;
	e[cnt].w=w;
	e[cnt].next=head[u];
	head[u]=cnt;
}
void dfs(int t,int fx){
	f[t]=fx;
	for(int i=head[t];i;i=e[i].next){
		if(fx==e[i].v) continue;
		deep[e[i].v]=deep[t]+e[i].w;
		if(deep[e[i].v]>deep[c])c=e[i].v;
		dfs(e[i].v,t);
	}
}
void work(int t,int f1){
	maxd=max(maxd,deep[t]);
	for(int i=head[t];i;i=e[i].next){
		if(isol[e[i].v]!=1&&e[i].v!=f1) deep[e[i].v]=deep[t]+e[i].w,work(e[i].v,t);
	}
}
signed main(){
	cin>>n;
	for(int i=1;i<n;i++){
		cin>>a>>b>>c;
		add(a,b,c);
		add(b,a,c);
	}
	dfs(1,0);
	deep[c]=0;
	int op=c;
	memset(deep,0,sizeof(deep));
	dfs(c,0);
	cout << deep[c] << endl;
	for(int i=c;i;i=f[i])
		isol[i]=1;
	int l=op,r=c;
	int ff=0;
	for(int i=f[c];i!=op;i=f[i]){
		//cout << i << " " << l << " " << r << endl;
		int id=deep[i];
		int ip=deep[c]-deep[i];
		maxd=deep[i]=0;
		work(i,0);
		if(maxd==id&&!ff) l=i,ff=1;
		else if(maxd==ip) r=i;
	}
	long long ans=0;
	for(int i=r;i!=l;i=f[i]) ans++;
	cout<<ans;
}

🍁链式前向星

这里为什么不使用邻接表呢

邻接表用链表实现,链式前向星用结构体实现,邻接表可以动态增边,相对好写,而链式前向星需要提前开好空间

这俩思路其实本质上差不多,但是链式前向星相比邻接表效率更高,也更加实用

🍁 如何理解

在链式前向星中,我们通常用一个结构体来表示

cpp 复制代码
struct node{
	int u,v,w,next;
}e[];

//建图
int cnt=0,head[];
void add(int u,int v,int w){
	e[++cnt].u=u;
	e[cnt].v=v;
	e[cnt].w=w;
	e[cnt].next=head[u];
	head[u]=cnt;
}

u: 代表起点
v:代表终点
w:代表权值
next:代表当前起点的上一条边,只代表边
head:代表当前点最大边的下标

相关推荐
shinelord明1 分钟前
【再谈设计模式】享元模式~对象共享的优化妙手
开发语言·数据结构·算法·设计模式·软件工程
დ旧言~7 分钟前
专题八:背包问题
算法·leetcode·动态规划·推荐算法
小俊俊的博客10 分钟前
海康RGBD相机使用C++和Opencv采集图像记录
c++·opencv·海康·rgbd相机
_WndProc25 分钟前
C++ 日志输出
开发语言·c++·算法
薄荷故人_26 分钟前
从零开始的C++之旅——红黑树及其实现
数据结构·c++
m0_7482400226 分钟前
Chromium 中chrome.webRequest扩展接口定义c++
网络·c++·chrome
qq_4335545434 分钟前
C++ 面向对象编程:+号运算符重载,左移运算符重载
开发语言·c++
努力学习编程的伍大侠38 分钟前
基础排序算法
数据结构·c++·算法
XiaoLeisj1 小时前
【递归,搜索与回溯算法 & 综合练习】深入理解暴搜决策树:递归,搜索与回溯算法综合小专题(二)
数据结构·算法·leetcode·决策树·深度优先·剪枝
yuyanjingtao1 小时前
CCF-GESP 等级考试 2023年9月认证C++四级真题解析
c++·青少年编程·gesp·csp-j/s·编程等级考试