题解 LGP5017【[NOIP2018 普及组] 摆渡车】

problem

\(n\) 名同学要乘坐摆渡车从人大附中前往人民大学,第 \(i\) 位同学在第 \(t_i\) 分钟去 等车。只有一辆摆渡车在工作,但摆渡车容量可以视为无限大。摆渡车从人大附中出发、 把车上的同学送到人民大学、再回到人大附中(去接其他同学),这样往返一趟总共花费 \(m\) 分钟(同学上下车时间忽略不计)。摆渡车要将所有同学都送到人民大学。

凯凯很好奇,如果他能任意安排摆渡车出发的时间,那么这些同学的等车时间之和最小为多少呢?

注意:摆渡车回到人大附中后可以即刻出发。

对于 \(100\%\) 的数据,\(n ≤ 500, m ≤ 100, 0 ≤ t_i ≤ 4 \times 10^6\)

solution 1

暴力不讲。

考虑 \(f_t\),表示 \(t\) 时刻有摆渡车出发,的最小等待时间。考虑如果有个 \(t\) 我们是能知道当前哪些人被送走的。

\[f_i=\min_{i-j\geq m}\{f_j+i(s_i-s_j)-t_i+t_j\}. \]

其中 \(s_i\) 是一个桶的前缀和,表示 \([1,i]\) 时间段有多少人在等车。\(t_i\) 是这些人等车时间的前缀和,拍到时间轴上。

有同学从 \(0\) 出发,要注意;最终的答案是后 \(m\) 个的最小值。

\[f_i=-\max_{i-j\geq m}\{is_j-f_j-t_j\}+is_i-t_i. \]

斜率为 \(s_j\),截距为 \(-f_j-t_j\),考虑李超线段树即可。

solution 2

\[\begin{aligned} f_i&=-is_j+f_j+t_j+is_i-t_i.\\ f_j+t_j&=is_j+f_i-is_i+t_i. \end{aligned}\]

Let \(y=f_j+t_j,k=i,x=s_j,b=f_i-is_i+t_i\), \(\therefore y=kx+b\).

\((x,y)\) 看作一个点,然后就是一条斜率为 \(k\) 的直线,从下往上扫到第一个点 \((x,y)\),这时这条直线的截距就最小,我们就胜利了。

维护一个凸包,然后发现 \(x,k\) 都单调。然后斜率优化就是了。

下面讲一下细节:

  • 我们会发现存在大量的点的 \(x,y\) 坐标相同。
  • 在入队和出队时我们要特判 \(x\) 坐标相同的,保留 \(y\) 较小的。具体看代码。
  • 由此引出的另一种做法是:只保留关键点。

code

调试方法:

  • 先打暴力。
  • 然后算一下每个点的 \(x,y\) 坐标,用暴力看一下是不是优秀的。
  • 输出队列中两个点之间的斜率,应该单调。
点击查看代码
#include <cstdio>
#include <vector>
#include <cstring>
#include <algorithm>
using namespace std;
#ifdef LOCAL
#define debug(...) fprintf(stderr,##__VA_ARGS__)
#else
#define debug(...) void(0)
#endif
typedef long long LL;
int q[4000010],n,m,maxk;
LL s[4000010],t[4000010],f[4000010];
LL dp(){
	f[0]=0;
	auto getx=[&](int i){return s[i];};
	auto gety=[&](int i){return f[i]+t[i];};
	auto getdx=[&](int i,int j){return getx(i)-getx(j);};
	auto getdy=[&](int i,int j){return gety(i)-gety(j);};
	auto slope=[&](int i,int j)->double{return 1.*getdy(i,j)/getdx(i,j);};
	int L=1,R=0; q[++R]=0;
	auto insert=[&](int i){
		while(L<R&&(getdx(q[R],i)?slope(q[R-1],q[R])>=slope(q[R],i):getdy(q[R],i)>=0)) R--;//相同比较 y 坐标
//		while(L<R&&(__int128)(gety(q[R-1])-gety(q[R]))*(getx(q[R])-getx(i))>(__int128)(gety(q[R])-gety(i))*(getx(q[R-1])-getx(q[R]))) R--;
		if(getdx(q[R],i)) q[++R]=i;
		else if(getdy(q[R],i)>=0) q[R]=i;//入队也不要乱入
	};
	auto cut=[&](int k){
		while(L<R&&slope(q[L],q[L+1])<k) L++;//切凸包的时候已经没有相同的 x 了,正常切即可
//		while(L<R&&(gety(q[L])-gety(q[L+1]))<k*(getx(q[L])-getx(q[L+1]))); 
		return q[L];
	};
	for(int i=1;i<=maxk;i++){
		debug("i=%d\n",i);
		f[i]=i*s[i]-t[i];
		if(i-m>=1) insert(i-m);
		int j=cut(i);
//		for(int l=L;l<=R;l++){int j=q[l];
		f[i]=min(f[i],f[j]+i*(s[i]-s[j])-t[i]+t[j]);
		debug("j=%d, f[i].update(%lld), at (%d,%d)\n",j,f[j]+i*(s[i]-s[j])-t[i]+t[j],getx(j),gety(j));
//		}
//		debug("f[%d]=%lld (trans from j=%d)\n",i,f[i],j);
		for(int j=L;j<=R;j++) debug("%d,",q[j]);
		debug("\n");
//		for(int j=L;j<R;j++) debug("%.3lf,",slope(j,j+1));
//		debug("\n");
//		debug("slope(%d,%d)=%.3lf\n",i-1,i,slope(i-1,i));
	}
	return *min_element(f+maxk-m,f+maxk+1);
}
int main(){
//	#ifdef LOCAL
//	 	freopen("input.in","r",stdin);
//	#endif
	scanf("%d%d",&n,&m);
	for(int i=1,x;i<=n;i++) scanf("%d",&x),x++,s[x]++,t[x]+=x,maxk=max(maxk,x+m);
	for(int i=1;i<=maxk;i++) s[i]+=s[i-1],t[i]+=t[i-1];//,debug("s[%d]=%lld,t[%d]=%lld\n",i,s[i],i,t[i]);
	printf("%lld\n",dp());
	return 0;
}

posted @ 2022-11-24 21:24  caijianhong  阅读(48)  评论(0编辑  收藏  举报