P8817 [CSP-S 2022] 假期计划题解

题目描述

小熊的地图上有 n 个点,其中编号为1的是它的家、编号为 2,3,…,n 的都是景点。部分点对之间有双向直达的公交线路。如果点 x 与 z1​、z1​ 与 z2​、……、zk−1​ 与 zk​、zk​ 与 y 之间均有直达的线路,那么我们称 x 与 y 之间的行程可转车 k 次通达;特别地,如果点 x 与 y 之间有直达的线路,则称可转车 0 次通达。

很快就要放假了,小熊计划从家出发去 4 个不同的景点游玩,完成 55 段行程后回家:家 →→ 景点 A →→ 景点 B →→ 景点 C →→ 景点 D →→ 家且每段行程最多转车 k 次。转车时经过的点没有任何限制,既可以是家、也可以是景点,还可以重复经过相同的点。例如,在景点 A →→ 景点 B 的这段行程中,转车时经过的点可以是家、也可以是景点 C,还可以是景点 D →→ 家这段行程转车时经过的点。

假设每个景点都有一个分数,请帮小熊规划一个行程,使得小熊访问的四个不同景点的分数之和最大。

输入格式

第一行包含三个正整数 n,m,k,分别表示地图上点的个数、双向直达的点对数量、每段行程最多的转车次数。

第二行包含 n−1 个正整数,分别表示编号为 2,3,…,n 的景点的分数。

接下来 m 行,每行包含两个正整数 x,y,表示点 x 和 y 之间有道路直接相连,保证 1≤x,y≤n,且没有重边,自环。

输出格式

输出一个正整数,表示小熊经过的 44 个不同景点的分数之和的最大值。

输入输出样例

输入 #1:

8 8 1
9 7 1 8 2 3 6
1 2
2 3
3 4
4 5
5 6
6 7
7 8
8 1

输出 #1:

27

输入 #2:

7 9 0
1 1 1 2 3 4
1 2
2 3
3 4
1 5
1 6
1 7
5 4
6 4
7 4

输出 #2:

7

一、75分算法

通过读题我们不难想到,这是一道很标准的图论题,可以采用处理后暴力枚举拿走大部分的分数,具体思路如下:首先经过处理(宽度优先搜索最佳)判断当前的点经过不超过k次中转可以到达哪些景点,然后进行枚举和剪枝,整体内容简单易理解,以下是完整代码和详细说明:

#include<bits/stdc++.h>
using namespace std;
const int N=50086;
int e[N],h[N],ne[N],idx,m,n,c,f[N],d[2505];
int st[2505][2505];//st[i][j]表示能否在k次中转内从i号景点转到j号景点 
long long a[N],ans;//注意范围,景点分数和答案用long long 
void add(int x,int y){//建边 
	e[++idx]=y;
	ne[idx]=h[x];
	h[x]=idx;
}
struct node{
	int t,s;
};
queue<int> q;
int main(){
	
	memset(h,-1,sizeof(h));
	int i,j,l,x,y,t;
	cin>>n>>m>>c;
	for(i=2;i<=n;i++){
		cin>>a[i];
	}
	for(i=1;i<=m;i++){
		cin>>x>>y;
		add(x,y);//双向建边 
		add(y,x);
	}
	for(j=1;j<=n;j++){
		memset(d,0x3f,sizeof(d));//每次记得初始化 
		d[j]=0;//在每次循环中的d[i]表示从j号结点出发,到达i号节点要经历几次中转。 
		q.push(j);
		while(q.size()){
			int t=q.front();
			q.pop();
			if(d[t]==c+1)continue;//使用宽搜,超出中转次数限制就无需继续搜索了 
			for(i=h[t];i!=-1;i=ne[i]){//遍历每一个相邻的景点 
				if(d[e[i]]>d[t]+1){
					st[j][e[i]]=1;
					q.push(e[i]);
					d[e[i]]=d[t]+1;//中转次数加一 
				}
			}
		}
	}
	for(i=2;i<=n;i++){
		if(st[1][i])//从家可以在k次中转内到达a号景点 
		for(j=2;j<=n;j++){
			if(st[i][j])//从a号景点可以在k次中转内到达b号景点 
			for(x=2;x<=n;x++){
				if(st[j][x])//从b号景点可以在k次中转内到达c号景点 
				for(y=2;y<=n;y++){
					if(st[x][y])//从c号景点可以在k次中转内到达d号景点 
					if(i!=j&&i!=x&&i!=y&&j!=x&&j!=y&&x!=y){
						if(st[y][1]){//从d号景点可以在k次中转内到达家
							ans=max(ans,a[i]+a[j]+a[x]+a[y]);
						}
					}
				}
			}
		}
	} 
	cout<<ans;//输出最终答案 
	return 0;
}

二、满分算法

满分算法的关键是要理清思路,枚举四个点的时间复杂度太高,即使采用剪枝的方法依旧会超时,但我们不妨转换思路,我们一旦确定了其中的两个点(b、c最合适,因为无需判断剩下的点是否能在中转限制次数内到达)如图所示:我们枚举确定b、c两点后只需判断a景点与家,d景点与家是否能够在中转限制次数内到达。

由图可得,剩下a,d两点只需要分别在与b、c相连的且分数值前三大的点中枚举,原因如下:

首先找到的a、d两点一定要与家相连,其次题目要求景点分数和最大,那么我们可以不用考虑剩下分值很小的点,假设找到的a与家和b相连且分数最大,并且这个a点又是符合条件的最佳d点,那分数是肯定不能同时计算的,所以其中的某一个要取得分第二大的点。

另外,如果这两个点中的某一个恰好是b或c(比如与b相连的点恰好找到了c,c也与家相连,但是c点已经通过枚举确定了,不能再次经过c点)那么一样不能取,所以最坏情况下要取到第三大的点。那么代码整体不变,最后枚举部分修改后,完整代码如下:

#include<bits/stdc++.h>
using namespace std;
const int N=2505,M=10005;
int n,m,k;
vector<int> g[N];
long long w[N],ans;
int st[N][N],f[N][5];
struct node{
	int num,step;
};
bool cmp(int a,int b){
	return w[a]>w[b];
}
void bfs(int x){
	queue<node> q;
	q.push({x,0});
	while(q.size()){
		node h=q.front();
		q.pop();
		if(h.step==k+1)continue;
		for(auto i:g[h.num]){
			if(st[x][i]>h.step+1&&i!=x){
				st[x][i]=h.step+1;
				q.push({i,h.step+1});
				if(st[1][i]<=k+1){
					f[x][4]=i;
					sort(f[x]+1,f[x]+5,cmp);
				}
			}
		}
	}
}
int main(){
	int i,j;
	cin>>n>>m>>k;
	for(i=2;i<=n;i++)cin>>w[i];
	for(i=1;i<=m;i++){
		int u,v;
		cin>>u>>v;
		g[u].push_back(v);
		g[v].push_back(u);
	} 
	memset(st,0x3f,sizeof(st));
	for(i=1;i<=n;i++){
		bfs(i); 
	}
	ans=0;
	int a,b,c,d;
	for(b=2;b<=n;b++){
		for(c=2;c<=n;c++){
			if(st[b][c]<=k+1){
				for(i=1;i<=3;i++){
					for(j=1;j<=3;j++){
						a=f[b][i];
						d=f[c][j];
						if(a&&d&&a!=d&&a!=c&&b!=d)ans=max(ans,w[a]+w[b]+w[c]+w[d]);
					} 
				}
			}
		}
	}
	cout<<ans;
	return 0;
}

以上就是题解的全部内容了,听懂了的小伙伴们可以去洛谷尝试一下:

[CSP-S 2022] 假期计划 - 洛谷icon-default.png?t=N7T8https://www.luogu.com.cn/problem/P8817

喜欢的小伙伴们请多多支持,本人因最近学业繁忙,更新周期时间可能有点长,但一定会持续为大家带来更有质量的题解的,希望大家多多支持,我们共同进步!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值