【NOIP2018】【洛谷P5017】摆渡车【DP】

题目大意:

题目链接:https://www.luogu.org/problemnew/show/P5017
n n n个人分别在 t 1 ∼ t n t_1\sim t_n t1tn的时间到达,一辆摆渡车要把这些人送到另外一个地方,摆渡车来回一次要 m m m的时间单位。求把这些人都送到的最短时间。


思路:

肯定可以先把 t t t排序。
我们知道,一个人到达后发车只会有两种情况:

  1. 摆渡车在他到达之前就到了。此时可以直接发车。
  2. 摆渡车在他到达后 k k k分钟才到。此时要等 k k k分钟才能发车。

可以先预处理出 s [ i ] [ j ] s[i][j] s[i][j],表示第 i i i个人到第 j j j个人做同一辆车的等待时间。那么就有
s [ i ] [ j ] = ∑ j = 1 n ∑ i = 1 j − 1 ∑ k = i j − 1 t j − t k s[i][j]=\sum^{n}_{j=1}\sum^{j-1}_{i=1}\sum^{j-1}_{k=i}t_j-t_k s[i][j]=j=1ni=1j1k=ij1tjtk
那么我们就设 f [ i ] [ j ] f[i][j] f[i][j]表示在 i i i个人到达后发车,第 i i i个人等了 j j j分钟时的最小等待时间。
那么肯定要枚举 j j j,表示前 j j j个人已经送到了目的地。
那么如果第 i i i个人到达时,摆渡车已经回来了,那么就可以直接发车(即第 i i i个人的等待时间为 0 0 0)。此时就有
f [ i ] [ 0 ] = m i n ( f [ i ] [ 0 ] , f [ j ] [ k ] + s [ j + 1 ] [ i ] ) f[i][0]=min(f[i][0],f[j][k]+s[j+1][i]) f[i][0]=min(f[i][0],f[j][k]+s[j+1][i])
其中 k k k表示枚举的第 j j j个人等待的时间。
那么如果第 i i i个人到达后摆渡车没有回来,那么第 i i i个人等待的时间就是
w = t j + k + m − t i w=t_j+k+m-t_i w=tj+k+mti
其中 t j + k + m t_j+k+m tj+k+m是摆渡车回到的时间。
那么就有
f [ i ] [ w ] = m i n ( f [ i ] [ w ] , f [ j ] [ k ] + s [ j + 1 ] [ i ] + ( i − j ) ∗ w ) f[i][w]=min(f[i][w],f[j][k]+s[j+1][i]+(i-j)*w) f[i][w]=min(f[i][w],f[j][k]+s[j+1][i]+(ij)w)
答案就是 m i n ( f [ n ] [ i ] ) ( i = 0 ∼ m ) min(f[n][i])(i=0\sim m) min(f[n][i])(i=0m)
时间复杂度 O ( n 2 m ) O(n^2m) O(n2m),足够过掉本题。


代码:

#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;

const int N=510;
const int M=210;
const int Inf=2e9;
int n,m,ans,w,t[N],f[N][M],s[N][N];

int main()
{
	scanf("%d%d",&n,&m);
	for (int i=1;i<=n;i++)
		scanf("%d",&t[i]);
	sort(t+1,t+1+n);
	for (int j=1;j<=n;j++)
		for (int i=1;i<j;i++)
			for (int k=i;k<j;k++)
				s[i][j]+=t[j]-t[k];
	memset(f,0x3f3f3f3f,sizeof(f));
	t[0]=-Inf;
	for (int i=0;i<=m;i++)  //初始化
	{
		f[0][i]=0;
		f[1][i]=i;
	}
	for (int i=2;i<=n;i++)
		for (int j=0;j<i;j++)
			for (int k=0;k<=m;k++)
			{
				w=t[j]+k+m-t[i];
				if (w>0)
					f[i][w]=min(f[i][w],f[j][k]+s[j+1][i]+(i-j)*w);
				else
					f[i][0]=min(f[i][0],f[j][k]+s[j+1][i]);
			}
	ans=Inf;
	for (int i=0;i<=m;i++)
		ans=min(ans,f[n][i]);
	printf("%d\n",ans);
	return 0;
}
  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
【18NOIP普及组】摆渡是一道经典的模拟题,可以通过模拟辆在两个岸边的运动来求解。下面是解题思路与实现代码: 1. 首先,我们需要定义一个结构体来表示辆的状态,包括位置、时间和方向: ```cpp struct Status { int pos, time; bool dir; // true表示从左到右,false表示从右到左 }; ``` 2. 接下来,我们需要定义一个队列来保存辆的状态。初始化时,将辆从左岸开始,状态设置为{0, 0, true},即在左岸位置为0,时间为0,方向为从左到右。将这个状态入队。 ```cpp queue<Status> q; q.push({0, 0, true}); ``` 3. 接下来,我们需要进行 BFS 搜索。每次从队列中取出一个状态 s,然后考虑所有可能的转移情况: - 从左岸到右岸:如果当前方向为从左到右,且当前位置在左岸,那么可以考虑从左岸到右岸的转移。需要判断右岸是否有空位,并且需要考虑当前时间是否晚于所有等待在右岸的辆到达的时间。如果可以转移,那么将转移后的状态插入队列中。 - 从右岸到左岸:如果当前方向为从右到左,且当前位置在右岸,那么可以考虑从右岸到左岸的转移。需要判断左岸是否有空位,并且需要考虑当前时间是否晚于所有等待在左岸的辆到达的时间。如果可以转移,那么将转移后的状态插入队列中。 - 没有转移:如果当前状态无法进行任何转移,那么就将其弹出队列。 ```cpp while (!q.empty()) { Status s = q.front(); q.pop(); // 左岸到右岸 if (s.dir && s.pos < n && (s.time < arrive_right[s.pos] || right.empty())) { Status t{s.pos + 1, s.time + t_left_right, s.dir ^ 1}; if (visited.find(t) == visited.end()) { visited.insert(t); q.push(t); } } // 右岸到左岸 if (!s.dir && s.pos > 0 && (s.time < arrive_left[s.pos - 1] || left.empty())) { Status t{s.pos - 1, s.time + t_right_left, s.dir ^ 1}; if (visited.find(t) == visited.end()) { visited.insert(t); q.push(t); } } // 无法转移 if (!s.dir && s.pos == 0) { ans = s.time; break; } } ``` 4. 最后,我们可以在 BFS 过程中维护一个 visited 集合来避免重复搜索。当辆到达右岸且无法进行任何转移时,即可得到最终答案。 完整代码如下:
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值