题目描述
凯凯很好奇,如果他能任意安排摆渡车出发的时间,那么这些同学的等车时间之和最小为多少呢?
注意:摆渡车回到人大附中后可以即刻出发。
输入描述:
第一行包含两个正整数n,m,以一个空格分开,分别代表等车人数和摆渡车往返一趟的时间。
第二行包含 n 个正整数,相邻两数之间以一个空格分隔,第 i 个非负整数代表第 i 个同学到达车站的时刻。
输出描述:
输出一行,一个整数,表示所有同学等车时间之和的最小值(单位:分钟)。
示例1
5 1
3 4 4 3 5
0
同学 1 和同学 4 在第 3 分钟开始等车,等待 0 分钟,在第 3 分钟乘坐摆渡车 出发。摆渡车在第 4 分钟回到人大附中。
同学 2 和同学 3 在第 4 分钟开始等车,等待 0 分钟,在第 4 分钟乘坐摆渡车 出发。摆渡车在第 5 分钟回到人大附中。
同学 5 在第 5 分钟开始等车,等待 0 分钟,在第 5 分钟乘坐摆渡车出发。自此 所有同学都被送到人民大学。总等待时间为 0。
示例2
5 5
11 13 1 5 5
4
同学 3 在第 1 分钟开始等车,等待 0 分钟,在第 1 分钟乘坐摆渡车出发。摆渡车在第 6 分钟回到人大附中。
同学 4 和同学 5 在第 5 分钟开始等车,等待 1 分钟,在第 6 分钟乘坐摆渡车出发。摆渡车在第 11 分钟回到人大附中。
同学 1 在第 11 分钟开始等车,等待 2 分钟;同学 2 在第 13 分钟开始等车,等待 0 分钟。他/她们在第 13 分钟乘坐摆渡车出发。自此所有同学都被送到人民大学。 总等待时间为 4。可以证明,没有总等待时间小于 4 的方案。
备注:
对于 10% 的数据,
对于 30% 的数据,
对于 50%的数据,
另有 20%的数据,
对于 100% 的数据,![]()
解答
一、引理
对于每个乘客,能搭载ta的车的发车时间只有 m 种情况;设这个乘客开始等候的时间是
证明
1、如果存在一种情况,其发车时间是
2、如果
二、基本思想
-
首先,题目给定我们的这 n 个人开始等候的时间是乱的,所以我们要先按照开始
等车的时间把这 n 个人排个序。
-
设
表示用摆渡车已经载了前 i 个人,且搭载了第 i 个人(不一定只搭载第 i 个人)的那趟摆渡车的发车时间是(
)的最小等候时间和。(
的意义与题意相同)
这里要注意:
同时还需要满足
因为如果,那这趟车就可以把第
个人也搭上了,违反了
状态的定义。
- 对于每个
,枚举上一趟摆渡车的出发时间。
等等!数据范围写着:
![]()
你跟我说枚举时间?你这最起码都
的时间复杂度了,怎么 AC ?
别着急啊,我还没说完呢。
-
其实引理已经告诉我们,我们不需要把整个
枚举完。
由引理可得,对于前
个乘客,每个乘客能搭载的摆渡车的发车时间只有 m 种情况,所以我们只需要枚举这
种情况即可。其他情况都是废的,不需要去考虑。
这样做的枚举量为
,相比之前直接枚举
的时间复杂度
来讲,已经是飞跃了。
-
接着,假设前一趟摆渡车已经载了前
个人,那么我们要做的就只有两件事:
- 再枚举一个
,得到
的最小值。
- 计算出第
个人到第
个人等候当前这趟摆渡车的等候时间和。
- 再枚举一个
- 在状态转移方程中的体现就是:
这里也有一个地方要注意:在枚举 k 和 l 时,要满足 。
- 首先, i 和 j 必须枚举,所以是
的时间复杂度。
- 其次, k 和 l 也是要枚举的,所以又是一个
。
- 最后,每次枚举
,都要计算一次
函数,而这个
函数的时间复杂度是
的。
三、程序实现 or 剪枝
-
我们可以发现,这 n 个人中有一些人开始等候的时间是相同的。
对于这些人,我们可以进行去重(开一个结构体,把相同时间的人压进一个结构体里面,结构体里则用一个变量表示这些人开始等候的时间,用另一个变量表示这些重复的人的人数)。
虽然这样做时间复杂度会多增加了
,但这样可以在
时减少很多繁琐的特判。
- 我们来关注一下这个式子:
对于每个,当 k 每增加一时,
的值就只会减掉
(
表示结构体中第 k 个元素的重复的人的数量)。
所以我们可以在枚举每个 i 和 j 时,就把 算出来(用一个变量
存起来),然后,每当 k 增加 1 ,
就减去
。
状态转移方程就变为:
这样一抽出来,时间复杂度就变成了
注意!接下来的内容全都是难点,请大家做好心理准备!
-
其实大家有没有想过,枚举
这个操作显得有些多余,可不可以省去呢?(毕竟只是求一个最小值而已,我求完一次就把这个最小值存起来不就行了吗?)
没错,上面的想法是正确的!
设
则之前的状态转移方程可以简化为:
可以在求每个
的时候顺带维护
这个时间复杂度足以通过本题了!!!
但是,这样做真的是对的吗?
假设有这样一个例子:
(数轴上的两个点表示的是乘客开始等候的时间,圈3和圈4表示两个乘客在(结构体)数组中的编号)
这时,我们直接用 来为
做
就不行了。
假设我们要求 的值(也就是说最后一趟车的发车时间和第4个乘客开始等候的时间相同的情况),当我们枚举到
的情况时,我们只能取
的情况。(如果
的话,前一趟车的时间就有可能和当前这趟车的时间重叠了,违反了
中状态的定义!)
也就是说,不能直接用 数组来
!(因为
包含了
到
的最小值,直接用会出错!)
那应该怎么办?(我在考场上最绝望的时刻就是思考这个问题的过程)
后来一想:可不可以直接特判呢?
事实证明,可以!
首先,对于每个,我们都为其开一个变量
作为一个“防护墙”。
对于每个 k ,如果 ,那么
就不适用于
这个状态(因为对于这个 k ,有些
加上 m 以后会和
重叠)
对于这些 k ,我们就重新帮它枚举一个 (满足
,不然超过了会重叠)去
。
由此也可以得出,如果,就可以直接退出当前循环了。(因为你没有
再去枚举了)
这样一来,状态转移方程就开始有些恶心了:
这样优化后的时间复杂度是多少呢?
首先,无论如何, 是一定要枚举的,所以时间复杂度至少会有
。
然后,我们可以把两个方程分开考虑:
-
第一个方程枚举了
,时间复杂度为
-
因为 k 和
总共只会计算 m 次(因为只有当
时才会进入第二种情况。而
每次至少增长 1,最慢也就是
而已)
所以第二条方程的时间复杂度为。
加起来就是 ,因为
,所以可以简化成
!这个时间复杂度可以通过本题!
至此,我们终于得出了此题的可行解法!
四、考场代码
我的代码在结构体边界上有一些预处理,这个重在意会即可相信大家应该看得懂
#include<stdio.h> #include<algorithm> using namespace std; const int maxn=502,maxm=102; const int INF=0x7fffffff; int f[maxn][maxm]; int Min[maxn]; int a[maxn]; struct Node { int pos,num; }Mem[maxn]; int sz; int col(int l,int r,int pos) { int res=0; for(int i=l;i<=r;i++) res+=(pos-Mem[i].pos)*Mem[i].num; return res; } int main(int argc, char const *argv[]) { int n,m; scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) scanf("%d",&a[i]); sort(a+1,a+n+1); Mem[0].pos=-m*2-2; a[0]=-1; for(int i=1;i<=n;i++) { if( a[i]^a[i-1] ) Mem[++sz].pos=a[i]; Mem[sz].num++; } Mem[sz+1].pos=Mem[sz].pos+m+2; for(int i=1;i<=n;i++) { for(int j=1;j<=m;j++) f[i][j]=INF; Min[i]=INF; } Min[0]=0; for(int i=1;i<=sz;i++) for(int j=0;j<min(m,Mem[i+1].pos-Mem[i].pos);j++) { int pos=Mem[i].pos+j,lpos=pos-m; int val=col(1,i,pos); f[i][j]=val; for(int k=0; k<i and Mem[k].pos<=lpos ;k++) { val-=(pos-Mem[k].pos)*Mem[k].num; //如果Mem[k+1].pos-1<=lpos,那么Min[k]照样能用(因为Mem[k+1].pos-1也是l的边界之一,只要l小于两个边界中较小的那个值,就可以直接用Min[k]) if( min( Mem[k].pos+m-1,Mem[k+1].pos-1 )<=lpos ) f[i][j]=min( f[i][j],Min[k]+val ); else { for(int kk=0; Mem[k].pos+kk<Mem[k+1].pos and Mem[k].pos+kk<=lpos and kk<m;kk++) f[i][j]=min( f[i][j],f[k][kk]+val ); } } Min[i]=min( Min[i],f[i][j] ); } printf("%d",Min[sz]); return 0; }