当前位置: 首页 > article >正文

蓝桥杯省赛真题打卡day4

[蓝桥杯 2013 省 A] 大臣的旅费

题目描述

很久以前,T 王国空前繁荣。为了更好地管理国家,王国修建了大量的快速路,用于连接首都和王国内的各大城市。

为节省经费,T 国的大臣们经过思考,制定了一套优秀的修建方案,使得任何一个大城市都能从首都直接或者通过其他大城市间接到达。同时,如果不重复经过大城市,从首都到达每个大城市的方案都是唯一的。

J 是 T 国重要大臣,他巡查于各大城市之间,体察民情。所以,从一个城市马不停蹄地到另一个城市成了 J 最常做的事情。他有一个钱袋,用于存放往来城市间的路费。

聪明的 J 发现,如果不在某个城市停下来修整,在连续行进过程中,他所花的路费与他已走过的距离有关,在走第 x−1 千米到第 x 千米这一千米中(x 是整数),他花费的路费是 x+10 这么多。也就是说走 1 千米花费 11,走 2 千米要花费 23。

J 大臣想知道:他从某一个城市出发,中间不休息,到达另一个城市,所有可能花费的路费中最多是多少呢?

输入格式

输入的第一行包含一个整数 n(n≤10^5),表示包括首都在内的 T 王国的城市数。

城市从 1 开始依次编号,1 号城市为首都。

接下来 n−1 行,描述 T 国的高速路(TT 国的高速路一定是 n−1 条)。

每行三个整数 Pi,Q,Di,表示城市 Pi 和城市 Qi 之间有一条高速路,长度为 Di(Di≤1000) 千米。

输出格式

输出一个整数,表示大臣J最多花费的路费是多少。

输入输出样例

输入 #1复制

5
1 2 2
1 3 1
2 4 5
2 5 4

输出 #1复制

135

说明/提示

样例解释:大臣 J 从城市 4 到城市 5 要花费 135 的路费。

时限 5 秒, 64M。蓝桥杯 2013 年第四届省赛

附:树的直径的定义

在一棵树中,每一条边都有权值,树中的两个点之间的距离,定义为连接两点的路径上边权之和,那么树上最远的两个点,他们之间的距离,就被称之为,树的直径。

树的直径的别称,树的最长链。

请注意:树的直径,还可以认为是一条路径,不一定是只是一个数值。

考察算法:图论---无向无环带权图,树的直径,dfs,bfs,邻接表,树形dp

思路:题目中说,如果不重复经过大城市,从首都到达每个大城市的方案都是唯一的,说明是无环图,城市与城市直接有距离,说明是带权的,又任一两个城市都可以到达,所以是无向的,所以可以将这个图转化为,题目让我们求花费路费最多是多少,而路费又与城市间距离有关,所以就转化为了求任意两点间距离的最大值,也就是求树的直径.先想一种暴力做法,枚举任意一个起点,再枚举除起点以外的任意一个点作为终点,求两点间的距离,取最大值.在一棵树中或一个图中求任意两点的距离,可以用dfs.处理输入时,可以采用邻接表(方便查找任意一个点的邻居)存储.优化:以首都为根,且树的直径一定经过根,将根结点一分为二,比如从根结点走到右边某个叶子结点一定是半径最长的,再以该叶子结点为根,走到左边某个叶子结点一定是直径最长的,所以该题可以用两次dfs,第一次dfs以编号为1的城市(首都)为起点,且起点固定的情况下,执行一次dfs可以遍历所有的点,以1为起点找到路径最长的终点,再以该终点为根结点,再次dfs.

两次dfs/bfs:

优点 : 可以通过一个新的数组记录路径信息(例如父节点与子节点之间的关系) 缺点 : 无法处理 负边权 .

实现过程 : 1、从任意一个节点出发,通过 BFS 或 DFS 对树进行一次遍历,求出与出发点距离最远的节点,记为 p。 2、从节点 p 出发,通过 BFS 或 DFS 再进行一次遍历,求出与 p 距离最远的节点,记为 q。 (p 是一个节点的最远的一个端点,那么从 p 出发的最远的端点就是直径的另一个端点) 为什么无法处理负边权?

 看上面这个图: 如果按照 DFS 或者 BFS 我们第一次 找到的最远距离的节点是 2 , 然后从 2 出发
到达的最远距离的节点是 1 ,所以得到的树的直径长度是 1 ,但我们从图中很容易看出来树的直径最长应该是 2.(用树形 DP 的话从下向上就可以得到最长的树的直径的长度)

#include<bits/stdc++.h>
#define x first
#define y second
#define endl '\n'
using namespace std;
const int N=1e5+5,INF=0x3f3f3f3f,mod=1e9+7,M=2*N;
typedef pair<int,int>PII;
typedef long long LL;
using namespace std;
int head[N * 2],edge[N * 2],Next[N * 2],ver[N * 2];
int vis[N],dist[N];
int n,p,q,d;
int tot = 0;
int maxd = 0;
int main() {
    ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	int BFS(int u);
    void add(int u,int v,int w);
	scanf("%d",&n);
	for(int i = 1; i < n; i ++) {
		scanf("%d%d%d",&p,&q,&d);
		add(p,q,d);                   //建立无向图
		add(q,p,d);
	}
	int u = BFS(1);
	int s = BFS(u);
	printf("第一次遍历得到的节点 : %d\n",u);
	printf("第二次遍历得到的节点 : %d\n",s); 
	return 0;
}
void add(int u,int v,int w) {
	ver[ ++ tot] = v,edge[tot] = w;
	Next[tot] = head[u],head[u] = tot;
	return ;
}
int BFS(int u) {
	queue<int>Q;
	while(!Q.empty()) Q.pop();
	memset(vis,0,sizeof(vis));            //每次遍历的时候记得对数组进行Clear
	memset(dist,0,sizeof(dist));  
	Q.push(u);
	int x,max_num = 0;
	while(!Q.empty()) {
		x = Q.front();
		Q.pop();
		vis[x] = 1;
		for(int i = head[x]; i ; i = Next[i]) {
			int y = ver[i];
			if(vis[y]) continue;
			vis[y] = 1;
			dist[y] = dist[x] + edge[i];//从上向下走,所以需要进行累加(这是与树形DP最大的不同)
			if(dist[y] > maxd ) {     //更新值和节点编号
				maxd = dist[y];
				max_num = y;
			}
			Q.push(y);   //每个新的节点都要加入到队列中,有可能与该节点相连的路径是比较长的
		}
	}
	return max_num;
}
#include<bits/stdc++.h>
#define x first
#define y second
#define endl '\n'
using namespace std;
const int N=1e5+5,INF=0x3f3f3f3f,mod=1e9+7,M=2*N;
typedef pair<int,int>PII;
typedef long long LL;
using namespace std;
vector<PII> G[N];        // 用 pair<int,int>来保存部分信息,相对于 结构体来说更加方便一点
int dist[N];
int n,p,q,d;
int main() {
    ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	void solve();
	scanf("%d",&n);
	for(int i = 1; i < n; i ++) {
		scanf("%d%d%d",&p,&q,&d);
		G[p].push_back(make_pair(q,d));
		G[q].push_back(make_pair(p,d));
	}
	solve();
	return 0;
}
void DFS(int u,int father,int value) {
	dist[u] = value;                               // 这种方式就不用进行对数组Clear了
	for(int i = 0; i < G[u].size(); i ++) {
		if(G[u][i].x != father) {
			DFS(G[u][i].x,u,value + G[u][i].y);
		}
	}
	return ;
}
void solve() {
	DFS(1,-1,0);
	int u = 1;
	for(int i = 1; i <= n; i ++) {                    // 遍历寻找最大值
		if(dist[i] > dist[u]) {
			u = i;
		}
	}
	int x = u;
	DFS(u,-1,0);                                      // 第二次进行找另一个端点
	for(int i = 1; i <= n; i ++) {
		if(dist[i] > dist[u]) {
			u = i;
		}
	}
	int s = u;
	printf("第一次遍历得到的最远的节点编号 : %d\n",x);
	printf("第二次遍历得到的最远的节点编号 : %d\n",s);
	return ;
}

第二种方法可以用树形dp.

优点 : 可以有效处理 负边权 缺点 : 对于记录路径的信息效率较低

简单分析 : 先通过递归的方式到叶子底部,然后通过自底向上的方式进行更新距离,找到最长路径。 (看下图,可以得到这棵树的直径是经过根节点 1 的 路径最长的链 5 -> 2 -> 1 和 经过根节点 1 的路径 次长链 3 -> 6 -> 1 两者之和 由此可得:树的直径 = (经过某个节点的) 最长链 + 次长链) -- 是路径长度

实现过程:设 D[x] 表示从节点 x 出发走向以 x 为根的子树,能够到达的最远距离。 设 x 的子节点为 y1,y2....yt,edge(x,y)表示边权,显然有: D[x] = max(D[yi] + edge(x,yi))(i 的范围是 1 - t) 也就是说,从 根节点出发,找到自己的最小辈,然后从最小辈向根节点更新,找一个最长的路径链。 只要子节点是最长的,那么我们更新到根节点时,这条链毫无疑问也就是最长的。 我们在找某个节点的最长链会发现一个问题,就是当前节点有有几个子节点,有一个我们没得选,当有多个时我们就需要选择最长的。 拿上面的图来说, 2 号节点有 两个子节点,我们就需要进行比较一下,100 > 2 + 30 ,所以我们应该选择 5 -> 2 这条链。

具体代码:

void dp(int x) {
	 vis[x] = 1;
	 for(int i = head[x]; i ; i = Next[i]) {
		int y = ver[i];
		if(vis[y]) continue;                          // 判断是否已经经过该节点
		dp(y);                                        // 继续向下寻找子节点
		ans = max(ans,dist[x] + dist[y] + edge[i]);   // 枚举从 x 节点出发的所有边,找一个最远的路径(看上面的 2 号节点)(dist[x] 是当前目前已知的最长的,
                                                              // 但是 x 可能有多个分支,所以需要枚举找最长的或者次长的,形成经过该节点的直径)
		dist[x] = max(dist[x],dist[y] + edge[i]);     // 经过枚举后 dist[x] 就不一定是当前最长的的,所以需要更新一下。
	 }

 本题代码:

#include<bits/stdc++.h>
#define x first
#define y second
#define endl '\n'
using namespace std;
const int N=1e5+5,INF=0x3f3f3f3f,mod=1e9+7,M=2*N;
typedef pair<int,int>PII;
typedef long long LL;
struct node{
	int num,cost;//点的编号和到这个点的距离
};
int ans;
void dfs(vector<node>g[],int vis[],int i,int j,int dist){
	//查看如果是直接邻居
	for(int k=0;k<g[i].size();k++){//访问i所有的邻居
		if(g[i][k].num==j){//i的直接邻居中有j
			ans=max(ans,dist+g[i][k].cost);
			return;
		}
	}
	vis[i]=0;
	//如果不是直接邻居,就从现有的直接邻居去连接
	for(int k=0;k<g[i].size();k++){
		if(vis[g[i][k].num]==-1)
			dfs(g,vis,g[i][k].num,j,dist+g[i][k].cost);
	}
	vis[i]=-1;
}
int check(int dist){
	/* 第一项是11,第二项是12,第三项是13....
	第n项是11+(dist-1),所以总和就是(11+11+(dist-1))*dist/2 */
	return (21+dist)*dist/2;
}
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	int n;
	cin>>n;
	vector<node>g[n+1];
	int vis[n+1];
	memset(vis,-1,sizeof vis);//初始化为-1
	for(int i=1;i<n;i++){
		int a,b,c;
		cin>>a>>b>>c;
		node p1={b,c};
		node p2={a,c};
		g[a].push_back(p1);
		g[b].push_back(p2);
	}
	//暴力求任意两点间距离,并维护最长距离
	for(int i=1;i<=n-1;i++){
		for(int j=i+1;j<=n;j++){
			dfs(g,vis,i,j,0);//i,j表示从i到j的距离
		}
	}
	cout<<check(ans)<<endl;
	return 0;
}
//暴力解法
#include<bits/stdc++.h>
#define x first
#define y second
#define endl '\n'
using namespace std;
const int N=1e5+5,INF=0x3f3f3f3f,mod=1e9+7,M=2*N;
typedef pair<int,int>PII;
typedef long long LL;
struct node{
	int num,cost;//点的编号和到这个点的距离
};
int ans;
int point=-1;
void dfs(vector<node>g[],int vis[],int start,int dist){
	vector<node>nei_start=g[start];//表示start的所有邻居的集合
	vis[start]=0;//表示被访问
	bool isLeaf=true;
	for(int k=0;k<nei_start.size();k++){
		int num=nei_start[k].num;//邻居点的标号
		if(vis[num]==-1){
			isLeaf=false;
			dfs(g,vis,num,dist+nei_start[k].cost);
		}
	}
	vis[start]=-1;
	if(isLeaf){
		if(dist>ans){//更新答案
			ans=dist;
			point=start;
		}
	}
}
int check(int dist){
	/* 第一项是11,第二项是12,第三项是13....
	第n项是11+(dist-1),所以总和就是(11+11+(dist-1))*dist/2 */
	return (21+dist)*dist/2;
}
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	int n;
	cin>>n;
	vector<node>g[n+1];
	int vis[n+1];
	memset(vis,-1,sizeof vis);//初始化为-1
	for(int i=1;i<n;i++){
		int a,b,c;
		cin>>a>>b>>c;
		node p1={b,c};
		node p2={a,c};
		g[a].push_back(p1);
		g[b].push_back(p2);
	}
	dfs(g,vis,1,0);
	ans=0;//重置答案
	dfs(g,vis,point,0);
	cout<<check(ans)<<endl;
	return 0;
}
//两次dfs优化做法
#include<bits/stdc++.h>
#define x first
#define y second
#define endl '\n'
using namespace std;
const int N=1e5+5,INF=0x3f3f3f3f,mod=1e9+7,M=2*N;
typedef pair<int,int>PII;
typedef long long LL;
int e[M],w[M],ne[M],h[M],idx;
int vis[M],far;//far为最远点
int ans;
int n;
void add(int a,int b,int c){
	e[idx]=b;
	w[idx]=c;
	ne[idx]=h[a];
	h[a]=idx++;
}
void dfs(int a,int val){
	vis[a]=1;
	if(val>ans){
		ans=val;
		far=a;
	}
	for(int i=h[a];i!=-1;i=ne[i]){
		if(!vis[e[i]])
			dfs(e[i],val+w[i]);
	}
}
int check(int dist){
	return (21+dist)*dist/2;
}
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	cin>>n;
	memset(h,-1,sizeof h);
	for(int i=1;i<n;i++){
		int a,b,c;
		cin>>a>>b>>c;
		add(a,b,c);
		add(b,a,c);
	}
	dfs(1,0);
	memset(vis,0,sizeof vis);
	dfs(far,0);
	cout<<check(ans)<<endl;
	return 0;
}
//两次dfs另一种做法
#include<bits/stdc++.h>
#define x first
#define y second
#define endl '\n'
using namespace std;
const int N=1e5+5,INF=0x3f3f3f3f,mod=1e9+7,M=2*N;
typedef pair<int,int>PII;
typedef long long LL;
int h[N*2],e[N*2],ne[N*2],ver[N*2];
int dist[N],vis[N];
int tot,ans;
int n;
void add(int u,int v,int w){
	ver[++tot]=v;
	e[tot]=w;
	ne[tot]=h[u];
	h[u]=tot;
}
void dp(int x){
	vis[x]=1;
	for(int i=h[x];i;i=ne[i]){
		int y=ver[i];
		if(vis[y]) continue;
		dp(y);
		ans=max(ans,dist[x]+dist[y]+e[i]);
		dist[x]=max(dist[x],dist[y]+e[i]);
	}
}
int check(int dist){
	return (21+dist)*dist/2;
}
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	cin>>n;
	for(int i=1;i<n;i++){
		int a,b,c;
		cin>>a>>b>>c;
		add(a,b,c);
		add(b,a,c);
	}
	ans=0;
	dp(1);
	cout<<check(ans)<<endl;
	return 0;
}
//树形dp做法

http://www.kler.cn/news/335944.html

相关文章:

  • 【AI大模型】深入Transformer架构:编码器部分的实现与解析(下)
  • 消费者Rebalance机制
  • 【C++ Primer Plus】4
  • Perl 子程序(函数)
  • 《普林斯顿概率论读本》中文版目录
  • 摩尔平台今日学习点
  • Maven 入门
  • NineData云原生智能数据管理平台新功能发布|2024年9月版
  • 大学生就业与招聘:Spring Boot系统设计
  • source insight 的开源替代
  • QT学习笔记2.1(安装部署_QT Creator安装)
  • docker的安装与启动——配置国内Docker源
  • 基于ssm疫情防控志愿者管理系统设计与实现
  • 类和对象的学习1
  • 【2024年最新】基于springboot+mysql就业信息管理系统
  • CSP-J/S 复赛算法 树形动态规划
  • 【目标检测】集装箱缺陷检测数据集1476张5类缺陷VOC+YOLO格式
  • 第二十四讲-进度控件QProgressBar
  • Kubernetes-Dashboard篇-01-为集群搭建Dashboard
  • Spring Boot 基于 Mockito 单元测试