蓝桥杯图专题

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

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

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

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

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

【输入格式】

输入的第一行包含一个整数n,表示包括首都在内的T王国的城市数

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

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

每行三个整数Pi, Qi, Di,表示城市Pi和城市Qi之间有一条高速路,长度为Di千米。
输出格式

输出一个整数,表示大臣J最多花费的路费是多少。
样例输入1
5
1 2 2
1 3 1
2 4 5
2 5 4
样例输出1
135
【输出格式】

大臣J从城市4到城市5要花费135的路费。

【思路】
以每一个城市为起点暴力搜索其能到达的最远的城市,最大的结果即为所求。可以通过75%的数据。

注意=
由于是无向图,在存储数据的时候边双向关联的。以所给样例为例,若标记像往常dfs一样在for循环内标记,会出现从1结点进入2结点,然后2结点又遍历1结点的情况,1结点城市走了两遍,这是不允许的。标记应该在进入递归函数时。

package _2013;

import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;



/**
* @author JohnnyLin
* @version Creation Time:2020年10月13日 下午12:45:32
* https://www.dotcpp.com/oj/problem1438.html3
* 

*/
public class _大臣的旅费 {
	static int n,ans;
	static List<Integer> [] node;
	static int map[][];
	static boolean vis[];
	static void dfs(int father,int dist) {
		//System.out.println(father+" "+dist);
		vis[father]=true;
		for(int i=0;i< node[father].size();i++) {
			int son=node[father].get(i);
			if(!vis[son]) {
				//vis[son]=true; 错误写法
				dist+=map[father][son];
				if(dist>ans) {
					ans=dist;
				}
				dfs(son, dist);
				dist-=map[father][son];
				//vis[son]=false; 错误写法
			}
		}
		vis[father]=false;
	}
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		/*
		0	1	2
			11	12	13	14
		 */
		Scanner reader=new Scanner(System.in);
		n=reader.nextInt();
		map=new int[n+1][n+1];
		node=new ArrayList[n+1];
		vis=new boolean[n+1];
		for(int i=1;i<=n;i++)
			node[i]=new ArrayList<>();
		for (int i = 1; i < n; i++) {
			int p=reader.nextInt();
			int q=reader.nextInt();
			int d=reader.nextInt();
			node[p].add(q);
			node[q].add(p);
			map[p][q]=d;
			map[q][p]=d;
		}
		for (int i = 1; i <=n; i++) {
			dfs(i, 0);
			vis=new boolean[n+1];
		}
		System.out.println((21+ans)*ans/2);
	}

}

另一种写法:
这题就是求一棵树上的距离最远的两个点的距离,也就是最长路径问题。
最长路径问题就是:
假设 s-t这条路径为树的直径,或者称为树上的最长路径。现有结论,从任意一点u出发搜到的最远的点一定是s、t中的一点,然后在从这个最远点开始搜,就可以搜到另一个最长路的端点,即用两遍广搜就可以找出树的最长路。

package _2013;
/**
* @author JohnnyLin
* @version Creation Time:2020年10月13日 下午2:52:17
*/
import java.util.ArrayList;
import java.util.Scanner;

public class _大臣的旅费1 {
	public static int n;
	static int Max=Integer.MIN_VALUE;
	//代表最长距离的起点城市
	static int point;
	//城市结构体
	//声明动态数组 ArrayList<edge> map1=new ArrayList<>();看成A数据类型 类似于Integer
	//数组:A [] map2=new A();
	//两句合在一起就相当于:声明了一个数组map2里面的数据元素是A类型的 而A类型的数据是edge类的
	static ArrayList<edge>[] map;
	static class edge{
		public int P,Q,D;
		public edge(int p,int q,int d) {
			P=p;
			Q=q;
			D=d;
		}
	}
	private static void getResult() {
		boolean [] vis=new boolean[n+1];
		//进行两遍深搜 第一遍深搜搜索出最长路径的起点,第二次深搜走出最长路径
		dfs(1,vis,0);
		//布尔数组再次初始化 
		vis=new boolean[n+1];
		dfs(point,vis,0);
		int res=(11+10+Max)*Max/2;
		System.out.println(res);
	}
	private static void dfs(int start,boolean[] vis,int dis ) {
		vis[start]=true;
		//枚举从start出发 可以到达的下一个城市
		for(int i=0;i<map[start].size();i++) {
			edge temp=map[start].get(i);
			if(!vis[temp.Q]) {//若这两个城市的高速公路没有访问过 则访问
				dis+=temp.D;
				if(dis>Max) {//若这条路的距离大于max 更新max
					Max=dis;
					point=temp.Q;//并且存储该城市
				}
				dfs(temp.Q,vis,dis);//沿着该点往下搜索
				//需要恢复状态 因为在该条高速路走到黑之后进行下一条高速路的深搜
				dis-= temp.D;
			}
		}
	}
	
	public static void main(String[] args) {
		Scanner reader=new Scanner(System.in);
		n=reader.nextInt();
		
		//由n个城市 每个城市都有自己的map
		map=new ArrayList[n+1];
		for(int i=1;i<=n;i++) 
			//将每个城市的map都实例化为存储edge(高速路)对象的链表
			map[i]=new ArrayList<edge>();
		for(int j=1;j<n;j++) {
			int p=reader.nextInt();
			int q=reader.nextInt();
			int d=reader.nextInt();
			map[p].add(new edge(p,q,d));
			map[q].add(new edge(q,p,d));
			}
			reader.close();
			getResult();
		
	}
}
	


生命之树
在X森林里,上帝创建了生命之树。
他给每棵树的每个节点(叶子也称为一个节点)上,都标了一个整数,
代表这个点的和谐值。上帝要在这棵树内选出一个非空节点集S,
使得对于S中的任意两个点a,b,都存在一个点列 {a, v1, v2, ..., vk, b} 
使得这个点列中的每个点都是S里面的元素,且序列中相邻两个点间有一条边相连。
在这个前提下,上帝要使得S中的点所对应的整数的和尽量大。
这个最大的和就是上帝给生命之树的评分。
经过atm的努力,他已经知道了上帝给每棵树上每个节点上的整数。
但是由于 atm 不擅长计算,他不知道怎样有效的求评分。
他需要你为他写一个程序来计算一棵树的分数。

「输入格式」
第一行一个整数 n 表示这棵树有 n 个节点。
第二行 n 个整数,依次表示每个节点的评分。
接下来 n-1 行,每行 2 个整数 u, v,表示存在一条 u 到 v 的边。
由于这是一棵树,所以是不存在环的。

「输出格式」
输出一行一个数,表示上帝给这棵树的分数。

「样例输入」
5
1 -2 -3 4 5
4 2
3 1
1 2
2 5

「样例输出」
8

「数据范围」
对于 30% 的数据,n <= 10
对于 100% 的数据,0 < n <= 10^5, 每个节点的评分的绝对值不超过 10^6 。

资源约定:
峰值内存消耗(含虚拟机) < 256M
CPU消耗  < 3000ms


这是一颗无向赋权图,要求是找一棵最大的连通块(生成树)。

在这里插入图片描述要求的就是:
在这里插入图片描述

package _2015;

import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

/**
* @author JohnnyLin
* @version Creation Time:2020年10月12日 下午11:04:40
*/
public class _t10生命之树 {
	static int n;
	static long ans;
	//每个数组元素为列表
	static List<Integer> nodeEdge[];
	static long[] value;
	static void dfs(int son,int father) {
		for(int i=0;i<nodeEdge[son].size();i++) {
			int next=nodeEdge[son].get(i);
			if(next!=father) {//相邻结点不是父结点
				//子树最大连通块为负值则不要了 比如说3号结点的子树为负值
				dfs(next, son);
				value[son]+=Math.max(0,value[next]);
				//System.out.println(value[next]);
			}
		}
		//根结点可能是负值
		ans=Math.max(ans,value[son]);
		//System.out.println(ans);

	}
	public static void main(String[] args) {
		Scanner reader=new Scanner(System.in);
		 n=reader.nextInt();
		 value=new long[n+1];
		 nodeEdge=new ArrayList[n+1];
		 for(int i=1;i<=n;i++) {
			 value[i]=reader.nextInt();
			 nodeEdge[i]=new ArrayList<Integer>();
		 }
		 for(int i=1;i<n;i++) {
			 int u=reader.nextInt();
			 int v=reader.nextInt();
			 nodeEdge[u].add(v);
			 nodeEdge[v].add(u);
		 }
		 dfs(1,-1);
		 System.out.println(ans);
	
		 
		 

	}

}

另一种写法:
参考了网上代码的

package 第六届;

import java.util.LinkedList;
import java.util.List;
import java.util.Scanner;

public class 生命树_网友版 {
	static int[] nodeValue;//存放各个点的和谐值
	static List<Integer>[] point;//存放各个点的包含的邻接边
	static int[] value;//存放以某个点为根节点情况下的最大和谐值和
	static int ans=0;//记录最大结果
	static void dfs(int son,int father) {
		value[son]=nodeValue[son];//以son为根节点,一开始时其最大和谐值为它本身
		for(int i=0;i<point[son].size();i++) {//枚举与son相连的结点
			int next=point[son].get(i);
			//当枚举的下一个结点是son先前经过的点father 跳过 防止绕圈
			if(next==father)continue;//如果都continue掉说明这个点是叶子结点
			dfs(next,son);
			//回溯(子节点平行状态都走完没得走了或者说状态没得转移了才会回溯)得到父节点与子节点和
			if(value[next]>0)//子节点的和谐值
				value[son]+=value[next];
			ans=Math.max(ans, value[son]);
		}
		
	}
	public static void main(String[] args) {
		Scanner reader=new Scanner(System.in);
		int n=reader.nextInt();
		nodeValue=new int [n+1];
		value=new int[n+1];
		point=new LinkedList[n+1];
		for(int i=1;i<=n;i++) {
			nodeValue[i]=reader.nextInt();
			point[i]=new LinkedList();
		}
		for(int i=1;i<n;i++) {
			int x=reader.nextInt();
			int y=reader.nextInt();
			point[x].add(y);
			point[y].add(x);
		}
		dfs(1,-1);//从0号结点开始搜索 假设它的父节点为-1可以为其他的无关量
		System.err.println(ans);
	}

}

注意:java里调用栈最多一万层,因此使用dfs最多可以通过n <= 10,30%的数据

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值