1582 -- 【最短路径】树的最长路径2897
乌托邦有n个城市,某些城市之间有公路连接。任意两个城市都可以通过公路直接或者间接到达,并且任意两个城市之间有且仅有一条路径。
每条公路都有自己的长度,这些长度都是已经测量好的。佳佳想从一个城市出发开车到另一个城市,并且她希望经过的公路总长度最长。请问她应该选择那两个城市?这个最长长度是多少?
第一行一个整数n(n<=100),以下n-1行每行三个整数a,b,c。表示城市a和城市b之间有公路直接连接,并且公路的长度是c(c<=10000)。
仅一个数,即最长长度。
https://blog.csdn.net/weixin_42979819/article/details/103943833
求直径
基本的求法是,先随便找一个点作为根结点转换为无根树后,遍历每一个点,
找出当i为根结点时的子树到叶子的最大距离d(j),在根据d(j)求出结点i作为根结点时整个树的最长路径,维护最长路径即可。
题解:
集合:挂在不同节点上面的直径的答案。
属性:最大值
last点:每个根不同答案可能不同。
状态:以谁为根。
这道题我们最长路径有可能是两种情况:
1,以这个点为直径端点的情况。
2,以这个点为中间节点,直径挂在这个节点上的情况。
对于第一种情况我们只需要找到两个端点的最大距离就好了。对于第二种情况。我们可以记录挂在这个节点上的最大值和次大值之和就是我们直径的最大值。
(因为有小伙伴问我最大值和次大值有没有可能在同一颗树上。这里建议大家可以画图看看,我们的最大值和次大值都是通过相邻的节点进行更新的,
所以次大值和最大值只可能是相邻的不同节点贡献的,所以一定不可能在同一颗树上)
#include<iostream> #include<cstring> #include<cmath> #include<algorithm> #include<stack> #include<cstdio> #include<queue> #include<map> #include<vector> #include<set> using namespace std; const int maxn=1010; const int INF=0x3fffffff; typedef long long LL; int n; int dis[1110]; //https://blog.csdn.net/weixin_42979819/article/details/103943833 int head[maxn],ne[maxn],w[maxn],e[maxn]; int cnt,ans; void add(int a,int b,int c){ e[cnt]=b; w[cnt]=c; ne[cnt]=head[a]; head[a]=cnt++; } int dfs(int u,int fa){ int dis=0;//距离这个根节点的最长距离 int d1=0,d2=0; //挂在这个根节点的最长距离和次长距离 for(int j=head[u];j!=-1;j=ne[j]){ int to=e[j]; if(to==fa) continue; int d=dfs(to,u)+w[j]; dis=max(dis,d); if(d>=d1){ d2=d1; d1=d; } else if(d>d2) d2=d; } ans=max(ans,d1+d2); return dis; } int main(){ cin>>n; int a,b,c; memset(head,-1,sizeof(head)); for(int i=1;i<=n-1;i++){ cin>>a>>b>>c; add(a,b,c); add(b,a,c); } dfs(1,-1); cout<<ans<<endl; return 0; }
2、求树的每个点与之距离最远的点的距离
dfs1:求出每个点向下的最长与次长
距离最远的选择:x向下的最长
x向上到fa的距离+fa的最长距离(这里有两种选择,要么是fa向上的最长距离,要么是看x与fa的向下最长关系选择的距离)
#include<cstdio> #include<cstring> #include<algorithm> using namespace std; const int MAXN=10000+200; struct edge { int to;//终端点 int next;//下一条同样起点的边号 int w;//权值 } edges[MAXN*2]; int tot;//总边数 int head[MAXN];//head[u]=i表示以u为起点的所有边中的第一条边是 i号边 void add_edge(int u,int v,int w)//添加从u->v,权值为w的边 { edges[tot].to=v; edges[tot].w=w; edges[tot].next = head[u]; head[u] = tot++; } int dist[MAXN][3];//dist[i][0,1,2]分别为正向最大距离,正向次大距离,反向最大距离 int longest[MAXN]; int dfs1(int u,int fa)//返回u的正向最大距离 { if(dist[u][0]>=0)return dist[u][0]; //记忆化 dist[u][0]=dist[u][1]=dist[u][2]=longest[u]=0; //递归边界 for(int e=head[u]; e!=-1; e=edges[e].next) { int v= edges[e].to; if(v==fa)continue; if(dist[u][0]<dfs1(v,u)+edges[e].w) //更新最长 { longest[u]=v; dist[u][1] = max(dist[u][1] , dist[u][0]); dist[u][0]=dfs1(v,u)+edges[e].w; }<br>//更新次长 else if( dist[u][1]< dfs1(v,u)+edges[e].w )//这里一定要记得,要不然无情WA dist[u][1] = max(dist[u][1] , dfs1(v,u)+edges[e].w); } return dist[u][0]; } void dfs2(int u,int fa) { for(int e=head[u];e!=-1;e=edges[e].next) { int v = edges[e].to; if(v==fa)continue; if(v==longest[u]) dist[v][2] = max(dist[u][2],dist[u][1])+edges[e].w; //如果在最长的一条上 else dist[v][2] = max(dist[u][2],dist[u][0])+edges[e].w; //不在最长的 dfs2(v,u); //后递归 } } int main() { int n; while(scanf("%d",&n)==1&&n) { tot=0; memset(dist,-1,sizeof(dist)); memset(head,-1,sizeof(head)); memset(longest,-1,sizeof(longest)); for(int i=2; i<=n; i++) { int v,w; scanf("%d%d",&v,&w); add_edge(i,v,w);//加边 add_edge(v,i,w);//加边 } dfs1(1,-1); dfs2(1,-1); for(int i=1;i<=n;i++) printf("%d ",max(dist[i][0],dist[i][2])); } return 0; }
3、求在直径上的点
1581:旅游规划
W 市的交通规划出现了重大问题,市政府下定决心在全市各大交通路口安排疏导员来疏导密集的车流。但由于人员不足,W 市市长决定只在最需要安排人员的路口安排人员。
具体来说,W 市的交通网络十分简单,由 nn 个交叉路口和 n−1n−1 条街道构成,交叉路口路口编号依次为 0,1,⋯,n−10,1,⋯,n−1 。任意一条街道连接两个交叉路口,且任意两个交叉路口间都存在一条路径互相连接。
经过长期调查,结果显示,如果一个交叉路口位于 W 市交通网最长路径上,那么这个路口必定拥挤不堪。所谓最长路径,定义为某条路径 p=(v1,v2,v3,⋯,vk)p=(v1,v2,v3,⋯,vk),路径经过的路口各不相同,且城市中不存在长度大于 kk 的路径,因此最长路径可能不唯一。因此 W 市市长想知道哪些路口位于城市交通网的最长路径上。
#include<iostream> #include<cstring> #include<cmath> #include<algorithm> #include<stack> #include<cstdio> #include<queue> #include<map> #include<vector> #include<set> using namespace std; const int maxn=2e5+10; const int INF=0x3fffffff; typedef long long LL; //https://www.cnblogs.com/JCNL666/p/10728048.html //这道题的本质是求出直径,然后求出在直径上的点 // dp一遍,求出一个点到除他所在的子树的最大距离。 //然后枚举所有点看看是不是字树内的最大距离加上子树外的最大距离等于直径长度。 //怎么求子树外的最大距离呢? //其实只要从上到下,从根到叶子结点转移。 // u ,他的儿子 v ,f[v] 表示 v 子树外的最远距离。 //那么如果这个点吧 d1[u] 更新了,那么他就不能从 d1[u] 哪里得到更新 //不然就可以。 int d1[maxn],d2[maxn],f[maxn]; //分别是最长路,次长路, 这个点向上能走的最长路 int n,max_dis=-1; vector<int> e[maxn]; void dfs1(int u,int fa){ //第一个DFS:求这棵树的直径 for(int i=0;i<e[u].size();i++){ int v=e[u][i]; if(v==fa) continue; dfs1(v,u); if(d1[v]+1>d1[u]){ //最长 d2[u]=d1[u]; d1[u]=d1[v]+1; } else if(d1[v]+1>d2[u]){ //次长 d2[u]=d1[v]+1; } } max_dis=max(max_dis,d1[u]+d2[u]); //直径 } void dfs2(int u,int fa){ int ans=0; for(int i=0;i<e[u].size();i++){ int v=e[u][i]; if(v==fa) continue; if(d1[v]+1==d1[u]) ans++; //个数 } for(int i=0;i<e[u].size();i++){ int v=e[u][i]; if(v==fa) continue; if(d1[u]!=d1[v]+1||(ans>1&&d1[u]==d1[v]+1)) f[v]=max(f[u],d1[u])+1; //如果v不在u的最长子树那条路里面,或者在但是这并不是唯一的一条最长路 //那么v到外面的最长路,除了可以考虑u到外面的最长路+1,或者也可以考虑u的像下面的最长路+1(到自己的兄弟 else f[v]=max(f[u],d2[u])+1; //不是的话,就可以考虑次长的兄弟,不能考虑自己呀,因为要么自己就是最长的,要么就是唯一的 dfs2(v,u); } } int main(){ scanf("%d",&n); int u,v; for(int i=1;i<n;i++){ scanf("%d %d",&u,&v); e[u].push_back(v); e[v].push_back(u); } dfs1(0,0); dfs2(0,0); for(int i=0;i<n;i++){ if(d1[i]+max(d2[i],f[i])==max_dis) printf("%d ",i); //到子树下面的最长路或者到外面的最长路加起来等于直径 } return 0; }