4.16天梯模拟赛补题——最短路+搜索

本文探讨了如何在给定的公路网络中找到出发地到目的地的最短路径及最低费用,同时介绍了如何在紧急情况下规划救援路径,考虑沿途集结最多救援队伍。通过Dijkstra算法的扩展,实现了兼顾距离和费用的路径选择,并处理了路径数量和援军数量的计算。此外,还涉及了图着色问题的解决方案验证,以及功夫传人问题中武功传承的计算,展示了图论在不同场景的应用。
摘要由CSDN通过智能技术生成

R7-2 旅游规划 (25 分)

有了一张自驾旅游路线图,你会知道城市间的高速公路长度、以及该公路要收取的过路费。现在需要你写一个程序,帮助前来咨询的游客找一条出发地和目的地之间的最短路径。如果有若干条路径都是最短的,那么需要输出最便宜的一条路径。

输入格式:
输入说明:输入数据的第1行给出4个正整数N、M、S、D,其中N(2≤N≤500)是城市的个数,顺便假设城市的编号为0~(N−1);M是高速公路的条数;S是出发地的城市编号;D是目的地的城市编号。随后的M行中,每行给出一条高速公路的信息,分别是:城市1、城市2、高速公路长度、收费额,中间用空格分开,数字均为整数且不超过500。输入保证解的存在。

输出格式:
在一行里输出路径的长度和收费总额,数字间以空格分隔,输出结尾不能有多余空格。

输入样例:

4 5 0 3
0 1 1 20
1 3 2 30
0 3 4 10
0 2 2 20
2 3 1 20

输出样例:

3 40

基础dijkstra板子的基础上加了一点变形,在更新距离的同时更新费用,如果距离相等则选择费用小的那一条路。

#include<bits/stdc++.h>
using namespace std;
#define INF 0x3f3f3f3f
const int N=510;
int n,m,s,d;
int dist[N],fee[N];
int edge[N][N],f[N][N],st[N];
void dijkstra()
{
	memset(dist,0x3f,sizeof dist);
	memset(fee,0x3f,sizeof fee);
	dist[s]=0,fee[s]=0;
	for(int i=0;i<n;i++)
	{
		int t=-1;
		for(int j=0;j<n;j++)
		{   
			if(!st[j]&&(t==-1||dist[t]>dist[j]))
			{
				t=j;//第一次搜出来的t一定是s 
			}
		} 
		if(st[t]) continue;
		st[t]=1;
		for(int j=0;j<n;j++)
		{  
			if(dist[t]+edge[t][j]<dist[j])
			{
				dist[j]=edge[t][j]+dist[t];
				fee[j]=f[t][j]+fee[t];
			}
			else if(dist[t]+edge[t][j]==dist[j])
			{
				fee[j]=min(fee[j],f[t][j]+fee[t]);
			}
		}
	
	}
}
int main()
{
	cin>>n>>m>>s>>d;
	int x,y,z,w;
	for(int i=0;i<n;i++)
	{
		for(int j=0;j<n;j++)
		{
			if(i==j) edge[i][i]=0,f[i][i]=0;
			else edge[i][j]=INF,f[i][j]=INF;
		}
	}
	for(int i=0;i<m;i++)
	{
		scanf("%d%d%d%d",&x,&y,&z,&w);
		edge[x][y]=edge[y][x]=z;
		f[x][y]=f[y][x]=w;
	}
	dijkstra();
	cout <<dist[d]<<" "<<fee[d]<< endl;
	return 0;
}

7-4 城市间紧急救援 (25 分)

作为一个城市的应急救援队伍的负责人,你有一张特殊的全国地图。在地图上显示有多个分散的城市和一些连接城市的快速道路。每个城市的救援队数量和每一条连接两个城市的快速道路长度都标在地图上。当其他城市有紧急求助电话给你的时候,你的任务是带领你的救援队尽快赶往事发地,同时,一路上召集尽可能多的救援队。

输入格式:
输入第一行给出4个正整数N、M、S、D,其中N(2≤N≤500)是城市的个数,顺便假设城市的编号为0 ~ (N−1);M是快速道路的条数;S是出发地的城市编号;D是目的地的城市编号。

第二行给出N个正整数,其中第i个数是第i个城市的救援队的数目,数字间以空格分隔。随后的M行中,每行给出一条快速道路的信息,分别是:城市1、城市2、快速道路的长度,中间用空格分开,数字均为整数且不超过500。输入保证救援可行且最优解唯一。

输出格式:
第一行输出最短路径的条数和能够召集的最多的救援队数量。第二行输出从S到D的路径中经过的城市编号。数字间以空格分隔,输出结尾不能有多余空格。

输入样例:

4 5 0 3
20 30 40 10
0 1 1
1 3 2
0 3 3
0 2 2
2 3 2

输出样例:

2 60
0 1 3

在上一道题的基础上的变形,增加了路径保存,路径条数的更新,援军数量的更新。每当更新到 j 的时候,让path[j]去记录来的节点 t , 如果遇到相等长度的路径说明需要把路径条数加和, 援军数量的更新就是在更新距离的时候顺便求和,注意一开始把每个节点的援军总量初始化为每个节点的最初援军数量,然后走一步更新一步,有点类似Dp。

#include<iostream>
#include<cstring>
int n,m;
int g[510][510];
using namespace std;
int path[510],dist[510],cnt[510],sum[510],fee[510];
int s,d;
bool st[510];
void dfs(int x)
{
	if(x==s)
	{
		printf("%d ",x);
		return ;
	}
	dfs(path[x]);
	if(x!=d) printf("%d ",x);
	else printf("%d\n",x);
}
void djst()
{
	memset(dist,0x3f,sizeof dist);
	dist[s]=0;
	cnt[s]=1;
	for(int i=0;i<n;i++)
	{   int t=-1;
		for(int j=0;j<n;j++)
		{
			if(!st[j]&&(t==-1||dist[j]<dist[t]))
			  t=j;
		}
		if(st[t]) continue;
		st[t]=true;
		for(int j=0;j<n;j++)
		{
			if(dist[j]>dist[t]+g[t][j])
			{
				dist[j]=dist[t]+g[t][j];
				sum[j]=sum[t]+fee[j];
				cnt[j]=cnt[t];
				path[j]=t;
			}
			else if(dist[j]==dist[t]+g[t][j])
			{
				cnt[j]+=cnt[t];
				if(sum[j]<sum[t]+fee[j])
				{
					sum[j]=sum[t]+fee[j];
					path[j]=t;
				}
			}
			
		}
	}
}
int main()
{   memset(g,0x3f,sizeof g);
	cin>>n>>m>>s>>d;
	for(int i=0;i<n;i++)
	{
		cin>>fee[i];
		sum[i]=fee[i];
	}
	while(m--)
	{
		int a,b,c;
		cin>>a>>b>>c;
		g[a][b]=g[b][a]=c;
	}
	djst();
	printf("%d %d\n",cnt[d],sum[d]);
	dfs(d);
}

7-11 图着色问题 (25 分)

图着色问题是一个著名的NP完全问题。给定无向图G=(V,E),问可否用K种颜色为V中的每一个顶点分配一种颜色,使得不会有两个相邻顶点具有同一种颜色?

但本题并不是要你解决这个着色问题,而是对给定的一种颜色分配,请你判断这是否是图着色问题的一个解。

输入格式:
输入在第一行给出3个整数V(0<V≤500)、E(≥0)和K(0<K≤V),分别是无向图的顶点数、边数、以及颜色数。顶点和颜色都从1到V编号。随后E行,每行给出一条边的两个端点的编号。在图的信息给出之后,给出了一个正整数N(≤20),是待检查的颜色分配方案的个数。随后N行,每行顺次给出V个顶点的颜色(第i个数字表示第i个顶点的颜色),数字间以空格分隔。题目保证给定的无向图是合法的(即不存在自回路和重边)。

输出格式:
对每种颜色分配方案,如果是图着色问题的一个解则输出Yes,否则输出No,每句占一行。

输入样例:

6 8 3
2 1
1 3
4 6
2 5
2 4
5 4
5 6
3 6
4
1 2 3 3 1 2
4 5 6 6 4 5
1 2 3 4 5 6
2 3 4 2 3 4

输出样例:

Yes
Yes
No
No

这道题就是dfs,并不是很难,这题写的稍微有点长主要是因为这几个测试点比较麻烦,比如这个小于k种颜色,或者还有可能测到大于k种颜色,总之要把颜色计数,如果不等于k种,直接输出“No"。再比如图不连通这个点,就要求我们搜1到n全部dfs一遍,不然可能会有不连通的子连通分量,而且那个子连通分量邻边同色我们是无法判断的。
在这里插入图片描述
代码:

#include<iostream>
#include<cstring>
using namespace std;
int n,m;
int q,k;
int g[510][510];
int vis[510];
int cnt[510];
int color[510];
int vv[510];
bool dfs(int x)
{
	vis[x]=1;
	for(int j=1;j<=n;j++)
	{
		
		if(g[x][j]==1&&color[j]==color[x])
		{
			return false;
		}
		else if(g[x][j]==1&&!vis[j]&&color[j]!=color[x])
		{
			if(!dfs(j)) return false;
		}
	}	
	return true;
}
int main()
{
	cin>>n>>m>>k;
	while(m--)
	{
		int a,b;
		cin>>a>>b;
		g[a][b]=g[b][a]=1;
	}
	cin>>q;
	while(q--)
	{   memset(vv,0,sizeof vv);
	    memset(vis,0,sizeof vis);
	    memset(cnt,0,sizeof cnt);
	    int cnt=0;
		for(int i=1;i<=n;i++)
		{  
			cin>>color[i];
			vv[color[i]]++;
		}
		for(int i=1;i<=510;i++)
		{
			if(vv[i]!=0) cnt++;
		}
		if(cnt!=k) 
		{
		printf("No\n");
		continue;
		}
		int flag=0;
		for(int i=1;i<=n;i++)
		{
			if(!dfs(i))
			{
				flag=1;
				break;
			}
		}
		if(flag) printf("No\n");
		else printf("Yes\n");
	}
}

7-10 功夫传人 (25 分)

一门武功能否传承久远并被发扬光大,是要看缘分的。一般来说,师傅传授给徒弟的武功总要打个折扣,于是越往后传,弟子们的功夫就越弱…… 直到某一支的某一代突然出现一个天分特别高的弟子(或者是吃到了灵丹、挖到了特别的秘笈),会将功夫的威力一下子放大N倍 —— 我们称这种弟子为“得道者”。

这里我们来考察某一位祖师爷门下的徒子徒孙家谱:假设家谱中的每个人只有1位师傅(除了祖师爷没有师傅);每位师傅可以带很多徒弟;并且假设辈分严格有序,即祖师爷这门武功的每个第i代传人只能在第i-1代传人中拜1个师傅。我们假设已知祖师爷的功力值为Z,每向下传承一代,就会减弱r%,除非某一代弟子得道。现给出师门谱系关系,要求你算出所有得道者的功力总值。

输入格式:
输入在第一行给出3个正整数,分别是:N(≤10^5)——整个师门的总人数(于是每个人从0到N−1编号,祖师爷的编号为0);Z——祖师爷的功力值(不一定是整数,但起码是正数);r ——每传一代功夫所打的折扣百分比值(不超过100的正数)。接下来有N行,第i行(i=0,⋯,N−1)描述编号为i的人所传的徒弟,格式为:K​i——ID[1] ID[2] ⋯ ID[K​i]其中Ki是徒弟的个数,后面跟的是各位徒弟的编号,数字间以空格间隔。K​i为零表示这是一位得道者,这时后面跟的一个数字表示其武功被放大的倍数。

输出格式:
在一行中输出所有得道者的功力总值,只保留其整数部分。题目保证输入和正确的输出都不超过10
​10
​​ 。

输入样例:

10 18.0 1.00
3 2 3 5
1 9
1 4
1 7
0 7
2 6 1
1 8
0 9
0 4
0 3

输出样例:

404

用链式前向星做出这道题还是蛮有成就感的,可以看出这是一棵树,有点不好下手,做完这道题才真正理解y总说的图和树是一样的这个概念。
这道题可以发现得道者是一棵树的叶子结点,也就是终止点,也是计数点,可以用Dfs搜到叶子结点并计数,在这个过程中不断更新衰减的功力值,当遍历到叶子结点的时候cnt += 当前功力值的X倍即可。我把标记叶子结点的数组充当标记数组的同时,里面的值也恰好是功力值放大倍数X。
至于最后向下取整的输出完全是参考样例试探出来的,应该是测评机的问题不用太在意。

#include<iostream>
#include<cstring>
using namespace std;
int n;
double cnt=0;
const int N=1e6+10;
double m,p;
double vis[N];
int h[N],e[N],ne[N],idx;
void add(int a,int b)
{
	e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
void dfs(int x,double gongli)
{   
	if(vis[x]!=0) 
	{
		cnt+=gongli*vis[x];
	}
	else 
	{   gongli*=p;
 		for(int i=h[x];i!=-1;i=ne[i])
		{
			int j=e[i];
			dfs(j,gongli);
		}
	}
	return ;
}
int main()
{   memset(h,-1,sizeof h);
    memset(vis,0,sizeof vis);
	cin>>n>>m>>p;
	p=(100-p)/100;
	for(int i=0;i<n;i++)
	{
		int num;
		scanf("%d",&num);
		if(num==0) 
		{   int x;
		    cin>>x;
			vis[i]=x;
		}
		else
		{
			while(num--)
			{
				int x;
				cin>>x;
				add(i,x);
			}
		}
	}
	dfs(0,m);
	int ct=cnt/1;//向下取整 
	printf("%d",ct);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值