题解[「JOISC 2017 Day 3」长途巴士]

题目

Luogu

Loj

AtCoder

推荐Loj,有翻译。

Sol

一道不那么容易看出来的DP题。难得一见的(O(n^2))式子难推而且要在单调栈上二分斜率

这道题被用来做模拟赛。wo bao li dou bu hui

首先要把(n^2)的式子推出来。

排序,(S_i)(S_i\%T)从大到小排 ,每个乘客按(D_i)从大到小排。

(f_i)为考虑了前(i)个人的最小费用,(sum_i)(1 ~ i) 乘客 (C_i) 的前缀和。

如果我们要把乘客(i)送到终点,那么:

[f_i=f_{i-1}+W(leftlfloordfrac{X-D_i}{T} ight floor+1) ]

意思是后面(X-D_i​)的路程都要让他下去接水。

如果我们要一些乘客下车,让(i)能刚好留下来

假设让(j in (0,i) ,j+1~i)的所有乘客下车:

[f_i=min(f_i,f_j+(i-j)*W*mn_i+sum_i-sum_j) ]

(mn_i)为到从起点走到特殊的取水点的距离时(j+1 ~ i)每个乘客必须要喝水的次数的最小值。

特殊的取水点指的是(D_i=<S_j\%T<=D_{i+1}) ,使它能刚好让(i)乘客下车。

[mn_i=min(leftlfloordfrac{S_j}{T} ight floor),D_i=<S_j\%P<=D_{i+1} ]

好了,到这里我们的(n^2)就可以啦。

下放代码。

然后我们开始(O(nlogn))的斜率的推导。

拆掉(min)

[f_i=f_j+i*W*mn_i-j*W*mn_i+sum_i-sum_j) ]

只与(i)有关的放一边,只与(j)有关的放一边:

[f_j-sum_j=W*j*mn_i+(f_i-i*W*mn_i) ]

括号里的是截距,要让(f_i)最小,(i*W*mn_i)又是定值,那就是让这个截距最小。

(x=W*j)(单调),斜率(k=mn_i)

由于(mn_i)不单调,所以我们要在单调栈里二分找与(i)匹配的斜率。

Y(o)Y!!!

Code

Code1

(O(n^2))

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=200010;
struct xbk{ll d,c;}a[N];
int n,m;
ll X,w,T,s[N],f[N],mn[N],sum[N],ans=1e18;
inline ll read(){
	ll w=0;
	char ch=getchar();
	while(ch>'9'||ch<'0') ch=getchar();
	while(ch>='0'&&ch<='9'){
		w=(w<<3)+(w<<1)+(ch^48);
		ch=getchar();
	}
	return w;
}
inline bool cmp(xbk a,xbk b){return a.d<b.d;}
int main(){
	X=read(),n=read(),m=read(),w=read(),T=read();
	for(int i=1;i<=n;i++) s[i]=read();
	s[++n]=X;
	for(int i=1;i<=m;i++) a[i].d=read(),a[i].c=read();
	sort(s+1,s+1+n);
	sort(a+1,a+1+m,cmp);
	for(int i=1;i<=m;i++) sum[i]=sum[i-1]+a[i].c;
	a[m+1].d=1e13;
	for(int i=1;i<=m;i++){
		mn[i]=1e13;
		for(int j=1;j<=n;j++)
			if((s[j]%T)>a[i].d&&(s[j]%T)<a[i+1].d){mn[i]=min(mn[i],s[j]/T);}
	} 
	for(int i=1;i<=m;i++){
		f[i]=f[i-1]+w*((X-a[i].d)/T+1);
		if(mn[i]==1e13) continue;
		for(int j=0;j<i;j++)
			f[i]=min(f[i],f[j]+w*(i-j)*mn[i]+sum[i]-sum[j]);
	}
	printf("%lld
",f[m]+(X/T+1)*w);
	return 0;
}

Code2

(O(nlogn))

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=200010;
struct xbk{ll d,c;}a[N];
int n,m,top,stk[N];
ll X,w,T,s[N],f[N],sum[N];
inline ll Y(int i){return f[i]-sum[i];}
inline double K(int i,int j){return (Y(i)-Y(j))/(i-j);}
inline ll read(){
	ll w=0;
	char ch=getchar();
	while(ch>'9'||ch<'0') ch=getchar();
	while(ch>='0'&&ch<='9'){
		w=(w<<3)+(w<<1)+(ch^48);
		ch=getchar();
	}
	return w;
}
inline int getpos(ll val){
	int l=1,r=top;
	while(l<r){
		int mid=(l+r)>>1;
		if(K(stk[mid],stk[mid+1])>val) r=mid;
		else l=mid+1;
	}
	return stk[r];
}
inline bool cmp(xbk a,xbk b){return a.d<b.d;}
inline bool cmp1(ll a,ll b){return a%T<b%T;}
int main(){
	X=read(),n=read(),m=read(),w=read(),T=read();
	for(int i=1;i<=n;i++) s[i]=read();
	s[++n]=X;
	for(int i=1;i<=m;i++) a[i].d=read(),a[i].c=read();
	sort(s+1,s+1+n,cmp1);
	sort(a+1,a+1+m,cmp);
	for(int i=1;i<=m;i++) sum[i]=sum[i-1]+a[i].c;
	a[m+1].d=1e13;
	stk[++top]=0;
	for(int i=1,j=0;i<=m;i++){
		f[i]=f[i-1]+((X-a[i].d)/T+1)*w;
		ll mn=1e13;
		while(s[j+1]%T<a[i].d&&j<n) j++;
		while(s[j+1]%T<a[i+1].d&&j<n) mn=min(mn,s[++j]/T);
		if(mn!=1e13){
			int pos=getpos(mn*w);
			f[i]=min(f[i],f[pos]+(i-pos)*w*mn+sum[i]-sum[pos]);
		}
		while(top>1&&K(stk[top],stk[top-1])>K(stk[top],i)) top--;
		stk[++top]=i;
	}
	printf("%lld
",f[m]+(X/T+1)*w);
	return 0;
}

完结撒花❀

原文地址:https://www.cnblogs.com/xxbbkk/p/14526445.html