ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

P5017 [NOIP2018 普及组] 摆渡车 题解

2022-07-22 22:03:46  阅读:146  来源: 互联网

标签:同学 10 cnt int 题解 sum NOIP2018 摆渡 P5017


P5017 [NOIP2018 普及组] 摆渡车

题目 [NOIP2018 普及组] 摆渡车

题目描述

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

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

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

输入格式

第一行包含两个正整数 \(n, m\),以一个空格分开,分别代表等车人数和摆渡车往返一趟的时间。
第二行包含 \(n\) 个正整数,相邻两数之间以一个空格分隔,第 \(i\) 个非负整数 \(t_i\) 代表第 \(i\) 个同学到达车站的时刻。

输出格式

输出一行,一个整数,表示所有同学等车时间之和的最小值(单位:分钟)。

样例 #1

样例输入 #1

5 1 
3 4 4 3 5

样例输出 #1

0

【数据规模与约定】
对于 \(10\%\) 的数据,\(n ≤ 10, m = 1, 0 ≤ t_i ≤ 100\)。
对于 \(30\%\) 的数据,\(n ≤ 20, m ≤ 2, 0 ≤ t_i ≤ 100\)。
对于 \(50\%\) 的数据,\(n ≤ 500, m ≤ 100, 0 ≤ t_i ≤ 10^4\)。
另有 \(20\%\) 的数据,\(n ≤ 500, m ≤ 10, 0 ≤ t_i ≤ 4 \times 10^6\)。
对于 \(100\%\) 的数据,\(n ≤ 500, m ≤ 100, 0 ≤ t_i ≤ 4 \times 10^6\)。

思路-1

将实际问题抽象后,不难发现这是一个 区间 \(DP\)

我们不妨认为时间是一条数轴,每名同学按照到达时刻分别对应数轴上可能重合的点。安排车辆的工作,等同于将数轴分成若干个左开右闭段,每段的长度 \(\geqslant m\)。原本的等车时间之和,自然就转换成所有点到各自所属段右边界距离之和

转移: \(f_i=min\{f_j+\sum^{j<t}_{k\leq i} i-t_k\}\)\(,\) \(j\leq i-m\)

但是这样显然时间复杂度会超标

考虑使用前缀和优化掉那个大大的 \(\sum\)

之后,转移式可以这样写: \(f_i=min\{f_j+(cnt_i-cnt_j)*i-(sum_i-sum_j)\}\) \(,\) \(j\leq i-m\)

这里令 \(t=max\{t_i\}\) \(,\) \(1\leq i \leq n\),最终答案只需在 \(i \geqslant t\) 找最小的 \(f_i\) 即可。实际上,\([t, t+m)\) 包含了所有可能的答案。

此时考虑时间复杂度:\(O(n^2)\) 非常不合理

考虑优化 \(DP\)

仍然考虑 \((j,i]\) 段的长度,由于分的段数不会增大答案,当它的长度 \(\geqslant 2m\) 时,我们完全可以再给它切一刀,得到不劣的答案。通过此性质,可剪去大量无用转移。

此时再来考虑时间复杂度:\(O(tm)\) 还是不够优秀 只能达到70pts

再考虑优化 \(DP\)

假设正在求 \(f_i\),但在 \((i-m,i]\) 中没有任何点,这个 \(f_i\) 相对来说就是 “无用” 的。原因是若最后一段长度恰好 \(= m\),这里面又没有任何点,不分割也罢。长度 \(>m\) 时,完全可以把这一段的右边界往左“拖”,产生不劣的答案。

然而直接扔掉这个状态,会与上一个优化缩小转移范围起冲突,故无用的位置令 \(f_i = f_{i-m}\),防止漏解。

此时的时间复杂度就已经非常优秀了:\(O(nm^2+t)\) 稳定100pts

CPP-1

#include <bits/stdc++.h>
using namespace std;
const int N=4e6+10;
const int INF=1e9;
int n,m,T;
int a[N],f[N],s[N];

inline int max(int a,int b) {
	return a>b?a:b;
}

inline int min(int a,int b) {
	return a<b?a:b;
}

inline int read() {
	int x, f = 1;
	char c;
	while (!((c = getchar()) >= '0' && c <= '9')) if (c == '-') f = -1;
	x = c - '0';
	while ((c = getchar()) >= '0' && c <= '9') (x *= 10) += c - '0';
	return x * f;
}

inline void write(int x) {
	if (x < 0) putchar('-'), x = -x;
	if (x > 9) write(x / 10);
	putchar(x % 10 ^ 48);
}


int main() {
	n=read(),m=read();
	for(int i=1; i<=n; i++) {
		int t=read();
		a[t]++;
		s[t]+=t;
		T=max(T,t);
	}
	for(int i=1; i<m+T; i++) {
		a[i]+=a[i-1];
		s[i]+=s[i-1];
	}
	for(int i=0; i<m+T; i++) {
		if(i>=m && a[i-m]==a[i]) {
			f[i]=f[i-m];
			continue;
		}
		f[i]=a[i]*i-s[i];
		for(int j=max(0,i-(m<<1)+1); j<=i-m; j++)
			f[i]=min(f[i],f[j]+(a[i]-a[j])*i-(s[i]-s[j]));
	}
	int ans=INF;
	for(int i=T; i<T+m; i++)
		ans=min(ans,f[i]);
	write(ans);
	putchar('\n');
	return 0;
}

思路-2

我们来考虑递推式:\(f_i=min\{f_j+(cnt_i-cnt_j)*i-(sum_i-sum_j)\}\) \(,\) \(j\leq i-m.\)

则有:\(f_i=f_j+cnt_i*i-cnt_j*i-sum_i+sum_j.\)

继续推导,则有:\(f_j+sum_j=f_i+sum_i-cnt_i*i.\)

不妨令 \(y=f_j+sum_j,k=i,x=cnt_j,b=f_i+sum_i-cnt_i*i.\)

这很显然就是一个斜率优化的式子了: \(y=kx+b.\)

而在DP的设置中,我们要求: \(i\geqslant j+m.\)

那么我们可以开始写代码了

需要注意的是:在斜率优化中我们难免会遇到求斜率的地方,而这种情况下程序无法避免的误差,可以通过二者相乘解决,详细可见我对P5785那道题的注释

CPP-2

#include <bits/stdc++.h>
using namespace std;
const int N=4e6+10;
int n,m,t,ti,ans=1e9;
int sum[N],cnt[N];
int q[N],f[N];
int l=1,r=0;

inline int read() {
    int x, f = 1;
    char c;
    while (!((c = getchar()) >= '0' && c <= '9')) if (c == '-') f = -1;
    x = c - '0';
    while ((c = getchar()) >= '0' && c <= '9') (x *= 10) += c - '0';
    return x * f;
}

inline double slope(int x,int y){
	return (f[y]+sum[y]-f[x]-sum[x])/(double)(cnt[y]==cnt[x]?1e-9:cnt[y]-cnt[x]);
}

int main(){
	n=read(),m=read();
	for(int i=1;i<=n;i++){
		ti=read();
		t=max(t,ti);
		cnt[ti]++;
		sum[ti]+=ti;
	}
	for(int i=1;i<t+m;i++){
		cnt[i]+=cnt[i-1];
		sum[i]+=sum[i-1];
	}
	for(int i=0;i<t+m;i++){
		if(i-m>=0){ // j = i - m
			while(l<r && slope(q[r-1],q[r])>=slope(q[r],i-m)) --r;
			q[++r]=i-m;
		}
		while(l<r && slope(q[l],q[l+1])<=i) ++l;
		f[i]=i*cnt[i]-sum[i];
		if(l<=r) f[i]=min(f[i],f[q[l]]+(cnt[i]-cnt[q[l]])*i-(sum[i]-sum[q[l]]));
	}
	for(int i=t;i<t+m;i++) ans=min(f[i],ans);
	printf("%d\n",ans);
	return 0;
}

标签:同学,10,cnt,int,题解,sum,NOIP2018,摆渡,P5017
来源: https://www.cnblogs.com/shen12345678/p/16508075.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有