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

文章讲述了如何使用广度优先搜索(BFS)算法解决洛谷竞赛中的问题,涉及在一个有向图中找到四个不同景点,使得经过的景点分数之和最大,同时考虑每段行程最多转车k次的限制。通过预处理和优化策略,避免了深度优先搜索可能带来的内存问题。
摘要由CSDN通过智能技术生成

原题目:P8817 [CSP-S 2022] 假期计划 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

题目描述

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

很快就要放假了,小熊计划从家出发去 4 个不同的景点游玩,完成 5 段行程后回家:家 →→ 景点 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分bfs搜索

我们第一时间可以想到用搜索(dfs/bfs)但由于dfs会用到栈,数据大可能会爆,所以我这里用bfs(队列)搜索四个点的最大得分

预处理(bef)

链式前向星双向建边:

int n,m,k,cnt;
long long ans;
int head[20005];
int di[2505][2505];
long long w[2505];
struct node3
{
	int num;
	long long w;
}wi[2505][10];
struct node
{
	int next,num,time;
}e[20005];
struct node2
{
	int time,num;
}; 
void add(int fa,int a)
{
	e[++cnt].num=a;
	e[cnt].next=head[fa];
	head[fa]=cnt;
}
queue <node2> q;

……省略

int i,x,y;
cin>>n>>m>>k;
for (i=2;i<=n;i++) cin>>w[i];
for (i=1;i<=m;i++)
{
	cin>>x>>y;
	add(x,y);
    add(y,x);
}

判断i号点在k次中转以内能到达的所有点j(di[i][j]=1): 

void bef()
{
	for (int i=1;i<=n;i++)
	{
		int dis[2505];
		memset(dis,0x3f,sizeof(dis));
		q.push({0,i});
		dis[i]=0;
		while (q.size())
		{
			node2 t=q.front();
			q.pop();
			if (t.time>k) continue;
			for (int j=head[t.num];j;j=e[j].next)
			{
				int to=e[j].num;
				if (dis[t.num]+1<dis[to])
				{
					di[i][to]=1;
					if (di[1][to])
					{
						wi[i][4].num=to;
						wi[i][4].w=w[to];
						sort(wi[i]+1,wi[i]+5,cmp);
					}
					dis[to]=dis[t.num]+1;
					q.push({dis[to],to});
				}
			}
		}
	}
}
枚举

有了每个点能到达的所有点,我们就可以枚举ABCD四个景点,将景点分数相加,记录最大得分(判断条件:a!=b!=c!=d && di[1][a]==di[a][b]==di[b][c]==di[c][d]==di[d][1]==1):

void bfs()
{
	int b,c,i,j;
	for (b=2;b<=n;b++)
	{
		for (c=2;c<=n;c++)
		{
			if (di[b][c] && b!=c)
			{
				for (i=1;i<=3;i++)
				{
					for (j=1;j<=3;j++)
					{
						int a=wi[b][i].num;
						int d=wi[c][j].num;
						if(a!=b && a!=c && a!=d && b!=d && c!=d && a && d)
						{
							ans=max(ans,w[a]+w[b]+w[c]+w[d]);
						}
					}
				}
			}
			
		}
	}
}

小技巧:我们可以将条件分开判断,减少搜索次数进行剪枝(可以多拿25分)

完整代码:

#include <bits/stdc++.h>
using namespace std;
int n,m,k,cnt;
long long ans;
int head[20005];
int di[2505][2505];
long long w[2505];
struct node
{
	int next,num,time;
}e[20005];
struct node2
{
	int time,num;
}; 
void add(int fa,int a)
{
	e[++cnt].num=a;
	e[cnt].next=head[fa];
	head[fa]=cnt;
}
queue <node2> q;
void bef()
{
	for (int i=1;i<=n;i++)
	{
		int dis[2505];
		memset(dis,0x3f,sizeof(dis));
		q.push({0,i});
		dis[i]=0;
		while (q.size())
		{
			node2 t=q.front();
			q.pop();
			if (t.time>k) continue;
			for (int j=head[t.num];j;j=e[j].next)
			{
				int to=e[j].num;
				if (dis[t.num]+1<dis[to])
				{
					di[i][to]=1;
					dis[to]=dis[t.num]+1;
					q.push({dis[to],to});
				}
			}
		}
	}
}
void bfs()
{
	int a,b,c,d;
	for (a=2;a<=n;a++)
	{
		if (di[1][a]==1)
		{
			for (c=2;c<=n;c++)
			{
				for (b=2;b<=n;b++)
				{
					if (di[a][b]==1 && di[b][c]==1 && a!=b &&b!=c && a!=c)
					{
						for (d=2;d<=n;d++)
						{
							if (di[d][1]==1 && di[c][d]==1 && a!=d && b!=d && c!=d)
							{
								ans=max(ans,w[a]+w[b]+w[c]+w[d]);
							}
						}
					}
				}
			}
		}
	}
}
int main ()
{
	int i,j,x,y;
	cin>>n>>m>>k;
	for (i=2;i<=n;i++) cin>>w[i];
	for (i=1;i<=m;i++)
	{
		cin>>x>>y;
		add(x,y);
		add(y,x);
	}
	bef();
	bfs();
	cout<<ans;
	return 0;
}

100分bfs搜索

预处理(bfs)同上 ↑

我们发现ABCD四个景点都是有联系的,我们只要能够走这四个景点,说明他们的di[i][j]都等于1,如果两两相互限制,例如已经知道景点A,C,那么B景点一定满足di[a][b]==di[b][c]=1,也就是说知道一个景点相邻的两个景点,就能锁定这个景点,别忘了,我们还有起点1,他也能为一个限制条件,所以我们可以推出,枚举BC景点是最优的,因为 1 ->A->B->C->D->1,我们再记录下3个与1号景点和i号景点di[][]值为1的最优值(记录3个的原因:枚举A,可能BC已经占了前两个最优值,所以D只能再记录一个值)就能进行优化

优化枚举
void bfs()
{
	int b,c,i,j;
	for (b=2;b<=n;b++)
	{
		for (c=2;c<=n;c++)
		{
			if (di[b][c] && b!=c)
			{
				for (i=1;i<=3;i++)
				{
					for (j=1;j<=3;j++)
					{
						int a=wi[b][i].num;
						int d=wi[c][j].num;
						if(a!=b && a!=c && a!=d && b!=d && c!=d && a && d)
						{
							ans=max(ans,w[a]+w[b]+w[c]+w[d]);
						}
					}
				}
			}
			
		}
	}
}

完整代码:

#include <bits/stdc++.h>
using namespace std;
int n,m,k,cnt;
long long ans;
int head[20005];
int di[2505][2505];
long long w[2505];
struct node3
{
	int num;
	long long w;
}wi[2505][10];
struct node
{
	int next,num,time;
}e[20005];
struct node2
{
	int time,num;
}; 
void add(int fa,int a)
{
	e[++cnt].num=a;
	e[cnt].next=head[fa];
	head[fa]=cnt;
}
queue <node2> q;
bool cmp(node3 a,node3 b)
{
	return a.w>b.w;
}
void bef()
{
	for (int i=1;i<=n;i++)
	{
		int dis[2505];
		memset(dis,0x3f,sizeof(dis));
		q.push({0,i});
		dis[i]=0;
		while (q.size())
		{
			node2 t=q.front();
			q.pop();
			if (t.time>k) continue;
			for (int j=head[t.num];j;j=e[j].next)
			{
				int to=e[j].num;
				if (dis[t.num]+1<dis[to])
				{
					di[i][to]=1;
					if (di[1][to])
					{
						wi[i][4].num=to;
						wi[i][4].w=w[to];
						sort(wi[i]+1,wi[i]+5,cmp);
					}
					dis[to]=dis[t.num]+1;
					q.push({dis[to],to});
				}
			}
		}
	}
}
void bfs()
{
	int b,c,i,j;
	for (b=2;b<=n;b++)
	{
		for (c=2;c<=n;c++)
		{
			if (di[b][c] && b!=c)
			{
				for (i=1;i<=3;i++)
				{
					for (j=1;j<=3;j++)
					{
						int a=wi[b][i].num;
						int d=wi[c][j].num;
						if(a!=b && a!=c && a!=d && b!=d && c!=d && a && d)
						{
							ans=max(ans,w[a]+w[b]+w[c]+w[d]);
						}
					}
				}
			}
			
		}
	}
}
int main ()
{
	int i,x,y;
	cin>>n>>m>>k;
	for (i=2;i<=n;i++) cin>>w[i];
	for (i=1;i<=m;i++)
	{
		cin>>x>>y;
		add(x,y);
		add(y,x);
	}
	bef();
	bfs();
	cout<<ans;
	return 0;
}

95分TLE解释:

在此说明:95分一个点TLE是因为bef()中第48行代码

if (dis[t.num]+1<dis[to])

不能带“=”,不然循环次数增加就超时了

 如果有帮助的话,给个赞吧👍

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值