[JOISC2017] 长途巴士 题解
问题的关键在于赶谁下车、最早啥时候能赶。显然赶下去的人会形成多段区间。我们把司机也当成乘客,同时对于所有人按 \(d_i\bmod t\) 的值升序排序,就会发现:
假如我们在某一时间点想要赶 \(i\) 下车,下一个收费站前最后一个想喝水的人是 \(j\),那么我们一定会把 \([i,j]\) 这个区间的人全部赶走。
由于司机是绝对不能下车的,所以所有被赶走的区间内一定没有司机。这就不用担心环了,可以直接开始 \(dp\) 了。
设 \(f_i\) 表示当前枚举到第 \(i\) 个乘客,已知的最小代价是多少,就有转移方程:
\[f_i=\min(f_{i-1}+w(\lfloor\frac xt\rfloor+[x\bmod t\ge d_i]),\min_{j=0}^{i-1}(f_j+sum_i-sum_j+mn_iw(i-j)))
\]
其中 \(sum_i=\sum\limits_{j=1}^ic_i\),\(mn_i=\min\limits_{d_i\le s_j\bmod t\le d_{i+1}}\lfloor\frac{s_j}t\rfloor\)。后者显然可以在给 \(s_j\) 排序后均摊 \(O(n)\) 求得。
\(dp\) 转移的后半项可以使用斜率优化。时间复杂度 \(O(n\log n)\)。
#include<bits/stdc++.h>
#define g(x) (f[x]-sm[x])
#define int long long
using namespace std;
const int N=2e5+5;
int num,n,m,w,t,f[N],sm[N],st[N],tp;
struct peo{int d,c;}pe[N],te[N];
int cmp(peo x,peo y){
return x.d<y.d;
}int chk(int i,int j,int k){
return (g(i)-g(j))*(j-k)<=(g(j)-g(k))*(i-j);
}int check(int x,int i){
return (i<2||g(st[i])-g(st[i-1])<=x*(st[i]-st[i-1]));
}signed main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>num>>n>>m>>w>>t;int cc=num%t;
for(int i=1;i<=n;i++) cin>>te[i].c,te[i].d=te[i].c%t;
for(int i=1;i<=m;i++) cin>>pe[i].d>>pe[i].c;
tp++,te[++n]={cc,num},sort(te+1,te+n+1,cmp);
sort(pe+1,pe+m+1,cmp),pe[m+1].d=1e18;
for(int i=1;i<=m;i++) sm[i]=sm[i-1]+pe[i].c;
for(int i=1,j=1,mn;i<=m;i++){
f[i]=f[i-1]+w*(num/t+(cc>=pe[i].d)),mn=1e18;
while(j<=n&&te[j].d<pe[i].d) j++;
while(j<=n&&te[j].d<pe[i+1].d) mn=min(mn,te[j++].c/t);
if(mn<1e18){
int l=1,r=tp,as;
while(l<=r){
int mid=(l+r)/2;
if(!check(w*mn,mid)) r=mid-1;
else l=mid+1,as=st[mid];
}f[i]=min(f[i],g(as)+sm[i]+w*(i-as)*mn);
}while(tp>1&&chk(i,st[tp],st[tp-1])) tp--;st[++tp]=i;
}cout<<f[m]+num/t*w+w<<"\n";
return 0;
}

浙公网安备 33010602011771号