假期计划(holiday)【CSPS2022】

题目描述

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

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

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

测试样例:

holiday.zip

输入输出格式

输入格式:

从文件holiday.in 中读入数据。
第一行包含3 个正整数n, m, k,分别表示地图上点的个数、双向直达的点对数量、每段行程最多的转车次数。
第二行包含n − 1 个正整数,分别表示编号为2, 3, . . . , n 的景点的分数。
接下来m 行,每行包含两个正整数x, y,表示点x 和y 之间有道路直接相连,保证1 ≤ x, y ≤ n,且没有重边,自环。

输出格式:

输出到文件holiday.out 中。
输出一个正整数,表示小熊经过的4 个不同景点的分数之和的最大值。

输入输出样例

输入样例#1:

【输入样例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

【输入样例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

输出样例#1:

【输出样例1】

27

【输出样例2】

7

【样例1 解释】
当计划的行程为1 → 2 → 3 → 5 → 7 → 1 时,4 个景点的分数之和为9+7+8+3 = 27,可以证明其为最大值。
行程1 → 3 → 5 → 7 → 8 → 1 的景点分数之和为24、行程1 → 3 → 2 → 8 → 7→ 1 的景点分数之和为25。它们都符合要求,但分数之和不是最大的。
行程1 → 2 → 3 → 5 → 8 → 1 的景点分数之和为30,但其中5 → 8 至少需要转车2 次,因此不符合最多转车k = 1 次的要求。
行程1 → 2 → 3 → 2 → 3 → 1 的景点分数之和为32,但游玩的并非4 个不同的景点,因此也不符合要求

当时考时脑子一塌糊涂,居然敢用搜索,爆搜的,没带剪枝,指数级......

0分:Game over!

题目分析

这道题乍看起来很复杂,其实我们可以把它抽象成两个部分:

题目原话:

每段行程最多转车k 次。转车时经过的点没有任何限制,既可以是家、也可以是景点,还可以重复经过相同的点。例如,在景点A → 景点B 的这段行程中,转车时经过的点可以是家、也可以是景点C,还可以是景点D → 家这段行程转车时经过的点。

所以,转车基本不会对后续操作产生影响,因为“转哪都无所谓”,但要求次数少于k次,因此,转多了的就不用管,只需考虑从i点到j点转车次数最少的方法,即最短路!

由于要求任意两点的最短路,首先可以想到Floyed算法,但n<=2500的数据范围让人不堪忍受。由于边权为1,可以采用广度优先算法实现:

1.广搜求最短路

其中:

add_edge(x,y)表示连由i到j的边,采用链式前向星实现。

step[j]表示队列第j个数到当前源点的距离。

vis[i]表示是否访问过点i.

    for(int i=1;i<=m;i++)
	{
		int x,y;
		cin>>x>>y;
		add_edge(x,y);
		add_edge(y,x);
	}
	for(int i=1;i<=n;i++)
	{
		memset(step,0,sizeof(step));
		memset(vis,0,sizeof(vis));
		memset(que,0,sizeof(que));
		que[1]=i;
		vis[i]=1;
		int hea=0,tail=1;
		while(hea<tail)
		{
			hea++;
			int u=que[hea];
			int sat=step[hea];
			d[i][u]=sat;
			if(d[i][u]-1>kk) d[i][u]=d[0][0];
			for(int j=head[u];j!=-1;j=edge[j].next)
			{
				int v=edge[j].to;
			
				if(vis[v]==0)
				{
					step[++tail]=step[hea]+1;
					
					que[tail]=v;
					vis[v]=1;
				}
			}
		}
	} 

时间复杂度:O(n^2)

第二部分,对于任意点i(i>1)有4种可能:

1.点i之前已经旅游过0个点

2.点i之前已经旅游过1个点

3.2个点

4.3个点

设dp[i][j]表示点i在第j种可能下能得到的最大分数。

则有dp[i][1]=点i的分数(d[1][i]>0&&d[1][i]<=2502)

dp[i][2]=max{dp[j][1]}+点i的分数(下称val[i])(d[i][j]>0&&d[i][j]<=2502)

dp[i][k]=max(dp[j][k-1]}+val[i] (d[i][j]>0&&d[i][j]<=2502)

最后找最大且能走到点1的d[i][4],输出。

可是,行程1 → 2 → 3 → 2 → 3 → 1 的景点分数之和为32,但游玩的并非4 个不同的景点,因此也不符合要求!因此,设vis2[i][k][j]表示点i在上文中第k种情况下j是否走过,设q[i][k][(1~4)]表示点i在上文中第k种情况下走过的所有点:

	for(int i=2;i<=n;i++)
	{
		if(d[1][i]<3000&&d[1][i]>=0)
		{
			dp[i][1]=val[i];
			vis2[i][1][i]=1;
			q[i][1][1]=i;
		}
	}

初始化。

    for(int k=2;k<=4;k++)
	{
		for(int i=2;i<=n;i++)
		{
			int tmp=0;
			for(int j=2;j<=n;j++)
			{
				if(d[i][j]==d[0][0]||i==j||d[i][j]<0) continue;
				if(vis2[j][k-1][i]==1) continue;
				if(dp[j][k-1]!=dp[0][0])
				{
					if(dp[j][k-1]>dp[i][k])
					{
						dp[i][k]=dp[j][k-1];
						tmp=j;
					}
				}
			}
			for(int j=1;j<=n;j++)
			{
				q[i][k][j]=q[tmp][k-1][j];
				vis2[i][k][q[i][k][j]]=1;
				if(q[i][k][j]==0)
				{
					q[i][k][j]=i;
					vis2[i][k][i]=1;
					break;
				}
			}
			dp[i][k]+=val[i];
		}
	}

动态规划。

    for(int i=2;i<=n;i++)
	{
		if(d[i][1]>0&&d[i][1]<3000)
		{
			maxn=max(maxn,dp[i][4]);
		}
	}
	cout<<maxn;

找最大值并输出。

完整:

#include<bits/stdc++.h>
using namespace std;
int n,m,kk,maxn=-1;
int d[2501][2501],cnt,dp[2501][4],vis2[2501][6][2501],q[2501][6][6];
int val[2501],vis[2501],head[2501],step[2501];
int que[2501];
struct Edge{
	int next;
	int to;
	int w;
}edge[30001];
void add_edge(int u,int v)
{
	cnt++;
	edge[cnt].to=v;
	edge[cnt].w=1;
	edge[cnt].next=head[u];
	head[u]=cnt;
}
int main()
{
	memset(d,0x3f,sizeof(d));
	memset(dp,-0x3f,sizeof(dp));
	memset(head,-1,sizeof(head));
	cin>>n>>m>>kk;
	for(int i=2;i<=n;i++)
	{
		cin>>val[i];
		d[i][i]=0;
	}
	for(int i=1;i<=m;i++)
	{
		int x,y;
		cin>>x>>y;
		add_edge(x,y);
		add_edge(y,x);
	}
	for(int i=1;i<=n;i++)
	{
		memset(step,0,sizeof(step));
		memset(vis,0,sizeof(vis));
		memset(que,0,sizeof(que));
		que[1]=i;
		vis[i]=1;
		int hea=0,tail=1;
		while(hea<tail)
		{
			hea++;
			int u=que[hea];
			int sat=step[hea];
			d[i][u]=sat;
			if(d[i][u]-1>kk) d[i][u]=d[0][0];
			for(int j=head[u];j!=-1;j=edge[j].next)
			{
				int v=edge[j].to;
			
				if(vis[v]==0)
				{
					step[++tail]=step[hea]+1;
					
					que[tail]=v;
					vis[v]=1;
				}
			}
		}
	} 
	for(int i=2;i<=n;i++)
	{
		if(d[1][i]<3000&&d[1][i]>=0)
		{
			dp[i][1]=val[i];
			vis2[i][1][i]=1;
			q[i][1][1]=i;
		}
	}
	for(int k=2;k<=4;k++)
	{
		for(int i=2;i<=n;i++)
		{
			int tmp=0;
			for(int j=2;j<=n;j++)
			{
				if(d[i][j]==d[0][0]||i==j||d[i][j]<0) continue;
				if(vis2[j][k-1][i]==1) continue;
				if(dp[j][k-1]!=dp[0][0])
				{
					if(dp[j][k-1]>dp[i][k])
					{
						dp[i][k]=dp[j][k-1];
						tmp=j;
					}
				}
			}
			for(int j=1;j<=n;j++)
			{
				q[i][k][j]=q[tmp][k-1][j];
				vis2[i][k][q[i][k][j]]=1;
				if(q[i][k][j]==0)
				{
					q[i][k][j]=i;
					vis2[i][k][i]=1;
					break;
				}
			}
			dp[i][k]+=val[i];
		}
	}
	for(int i=2;i<=n;i++)
	{
		if(d[i][1]>0&&d[i][1]<3000)
		{
			maxn=max(maxn,dp[i][4]);
		}
	}
	cout<<maxn;
	return 0;
 } 

  • 13
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值