题目描述
小熊的地图上有 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] 假期计划 - 洛谷https://www.luogu.com.cn/problem/P8817
喜欢的小伙伴们请多多支持,本人因最近学业繁忙,更新周期时间可能有点长,但一定会持续为大家带来更有质量的题解的,希望大家多多支持,我们共同进步!