码迷,mamicode.com
首页 > 其他好文 > 详细

摆渡车

时间:2019-05-03 20:03:13      阅读:143      评论:0      收藏:0      [点我收藏+]

标签:就会   部分   辅导   代码实现   意义   要求   name   type   输入数据   

#include<bits/stdc++.h>
using namespace std;
int n,m,t2;
int ans=0x3f3f3f3f,a[100000000],t1,t[100000000],f[100000000];
int main(){
scanf("%d%d",&n,&m); 
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&t1);
		a[t1]++;
		t[t1]+=t1;
		t2=max(t2,t1);
	}
	for(int i=1;i<t2+m;i++)
	{
		a[i]+=a[i-1];
		t[i]+=t[i-1];
	}
	for(int i=0;i<t2+m;i++)
	{
		if(i>=m&&a[i-m]==a[i])
		{
			f[i]=f[i-m];
			continue;
		}
		f[i]=a[i]*i/*需要用多少次i这个车停靠的点*/-t[i];// 特判(-∞, i] 单独作为一段的边界情况; 
		for(int j=max(i-2*m+1,0);j<=i-m;j++)
		{
			f[i]=min(f[i],f[j]+(a[i]-a[j])*i-(t[i]-t[j])); 
		}
	}
	for(int i=t2;i<t2+m;i++)
	{
		ans=min(ans,f[i]);
	}
	cout<<ans;
	return 0;
}
一道异常bt的dp,考试的时候完全找不到思路与表达式,所幸现在做出来了,思路如下:

技术图片

我们把这一条时间轴分成几段,最后一段边界为i,j,再设k为在第k时刻,其中j<k<=i,f[i]即为本题状态,即0到i中这一段的人们总候车时间。f[j]为上一个状态,不断用j更新i嘛,都是这样的,注意i,j是随时都在变的。

首先我们要知道这一段的长度,有人会想如果长度是<m(m是车的一趟来回)(最开始我也这样想)。but it is fool,我们把这个时间轴分成几段是吧,我们所划分的就是(我们假想的车一来一回,偶尔为了最优还要等几分钟)这种为一段,那么这一来一回就必然有了m,所以长度是>=m,说白了就是模拟,稍微想想就知道了。

我们已知j<k<=i,我们可以这样想,j,i即车到了的状态(即上一次出发和这一次出发),因为每一个时刻车都有可能因为不同的计划,安排调整,所以每一个时刻都有可能有车出现,反正出发时间随便你调,所以这是肯定的,每一个f[i]都表示车到了后,j到i这一段的总候车时间加上f[j]这一段的总候车时间。而我们要求f[i],j到i有i-j个k,(注意不加一原因请看j<k<=i)那么求第k时刻的候车时间(k到i注意直接用i-k乘以k时刻的人数),然后不断更新k值就ok了,注意还要加上f[j]。

这就是那个转移式的理解了,难点在于边界的随机划分,i,j不是很好理解。

只要这样想,我们求的是f[i],我们在求f[i]的时候,之前还有i-m个状态(其实不是i-m个,后面会提到,i-m个状态怎么出现的请看上文)。

每一个状态都有可能到达f[i],我们把那些状态统统看成f[j],每一个之前最优的f[j]再加上j到i的候车时间得到i个f[i],不断更新f[i]得到最小f[i],说白了就是分成两部分球f[i]。由f[j]的局部最优慢慢到f[i],到i达到边界时就会成为全局最优了。(只能说能想出这个的人真的厉害,我也只是看懂了,而且是暂时的,凭我的记忆力,过几天很可能又忘记怎么写了。)

当然这只是关于表达式的理解,还有很多优化,以及一些细节需要讲解。

首先就是求出每一个f[i]的临界值,即只在i点停下,前面的人都给我等着的这种极端情况。

为什么要求?因为这些情况在i比较小的时候是很有可能出现的啊。

然后想一想,怎么求i到j的人们的总等候时间?聪明的你想出来了吗?
这里我们用到了前缀和。求总等候时间不外乎就是一遍一遍的在j<k<=i中,一直k时刻的人数*(i-k),k不断更新,且不断累加,是吧。那么总的来说j到i有多少个人就要有多少个i来减是吧,我们可以在输入数据的时候直接求出到i时刻时候的人数减去到j时刻的人数,就是i到j的总人数,就知道需要多少个i了,即k时刻的人数*(i-k)打开后k时刻的人数*i这一个式子的含义。那么k时刻的人数*k又怎么表示呢?我们发现,不断累加后,k时刻的人数*k即为i到j这一段人们开始等车时间的总和。所以可以用前缀和,用i时刻人们开始等车时间的总和减去j时刻人们开始等车时间的总和即可得到。

代码实现f[j]+(a[i]-a[j])*i-(t[i]-t[j]。
至于输入时的前缀和怎么打大家看代码就能懂了。
但这样会超时
接下来是优化
首先是
if(i>=m&&a[i-m]==a[i])
		{
			f[i]=f[i-m];
			continue;
		}
到最后如果i到i+m这一段根本就没人了,还算他干什么。
由j=0优化成j=max(i-2*m+1,0),why?
剪枝。
i-m为j最大的情况(原因上面已经不能再详细了。),那么i-2m+1这个点即为有意义的j的最小情况。
j是上一趟嘛,i是这一趟嘛,你两趟之间等待时间总不能超过运行时间m吧。超过了那么必然有其他最优解(比如我不间断走两趟)来代替这一情况使等待时间最小。
所以完全可以不算了,用那些f[j]算出的答案是必然要舍弃的。
最后看边界
	for(int i=t2;i<t2+m;i++)
	{
		ans=min(ans,f[i]);
	}
即看这几种要么刚好在t2时刻出发的情况和t2+m时刻这之间出发哪一种人们等待时间最少,因为是都运完了所有人的不同时刻的各自的最优选择,不一定刚好t2那个是最优的,因为虽然t2时刻的人没有等,但可能你会让前面时刻的人多等很多,所以需要比较。
这可以说是手把手幼儿式一对一辅导。
如果看了我的题解都做不出的人我只能为你默哀。

摆渡车

标签:就会   部分   辅导   代码实现   意义   要求   name   type   输入数据   

原文地址:https://www.cnblogs.com/xxmxxm/p/10806124.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!