🏆今日学习目标:
🍀例题讲解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:代表当前点最大边的下标