CodeForces 311B - Cats Transport
洛谷又关发题解入口了((
洛谷题目页面传送门 & CodeForces题目页面传送门
一条直线上有\(n\)座山,从左往右编号依次为\(1\sim n\),第\(i-1,i\)座山的距离为\(d_i\)个单位时间走的路程。有\(m\)只猫,第\(i\)只去第\(h_i\)座山玩到时刻\(t_i\)。有\(s\)个铲屎官,每个铲屎官可以从任意时刻出发,从第\(1\)座山往第\(n\)座山走一趟,中间不停,抱走所有的未被抱走的且玩完的猫。第\(i\)只猫的等待时间为它被抱走的时刻减去\(t_i\)。求最小的所有猫的总等待时间。
\(n\in\left[2,10^5\right],m\in\left[1,10^5\right],s\in[1,100]\)。
考虑某铲屎官从时刻\(x\)出发,那么显然第\(i\)只猫可能被他抱走当且仅当\(x+\sum\limits_{j=2}^{h_i}d_j\geq t_i\)。关于\(i\)的放一边,得\(t_i-\sum\limits_{j=2}^{h_i}d_j\leq x\)。令\(a_i=t_i-\sum\limits_{j=2}^{h_i}d_j\)。若真的被此铲屎官抱走了,那么等待时间为\(x-a_i\)。
于是问题转化为了:有\(m\)个数,第\(i\)个数为\(a_i\)。要求给出一个数集\(S(|S|=s)\),\(\forall i\in[1,m]\),计算\(S\)中最小的\(\geq a_i\)的数减去\(a_i\),答案为这些差的和。求最小的和。
考虑将\(a\)排序。那么很容易得出结论:“\(S\)中最小的\(\geq a_i\)的数”是非严格单调递增的。于是减去的数们是由不超过\(s\)个严格单调递增的相等段组成的。
考虑DP。设\(dp_{i,j}\)表示前\(j\)个数,用了\(i\)个相等段(可以为空)时的最小的差的和。边界:\(dp_{0,j}=\begin{cases}0&j=0\\+\infty&j>0\end{cases}\),目标:\(dp_{s,m}\)。转移的话,考虑枚举倒数第二个相等段的右端点(倒数第一个相等段的右端点为\(j\)),由于\(a\)的单调性,最后一个相等段的值显然为\(a_j\)最优。考虑预处理出\(a\)的前缀和数组\(Sum\),那么状态转移方程很容易列出:\(dp_{i,j}=\min\limits_{k=0}^j\{dp_{i-1,k}+a_j(j-k)-(Sum_j-Sum_k)\}\)。
直接转移\(\mathrm O\!\left(m^2s\right)\)。这一看就是个斜率优化的模型,考虑对每个\(i\)做一次斜率优化:对于\(2\)个决策\(k<o\),\(k\)比\(o\)优当且仅当
变形后,由\(k<o\)得\(k-o<0\),除过去不等号变方向,得
再基础不过了根据\(a\)的单调性,单调队列维护下凸壳即可。
时间复杂度\(\mathrm O(n+m\log m+ms)\)。空间可以滚动数组优化到\(\mathrm O(n+m)\)。
代码:
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int inf=0x3f3f3f3f3f3f3f3f;
const int N=100000,M=100000;
int n,m,s;
int d[N+1];
int a[M+1],Sum[M+1];
int dp[2][M+1];//滚动数组优化
int q[M],head,tail;//单调队列
double f(int i,int k,int o){//斜率
return 1.*((dp[i-1&1][k]+Sum[k])-(dp[i-1&1][o]+Sum[o]))/(k-o);
}
signed main(){
cin>>n>>m>>s;
for(int i=2;i<=n;i++)cin>>d[i],d[i]+=d[i-1];
for(int i=1;i<=m;i++){
int h,t;
cin>>h>>t;
a[i]=t-d[h];
}
sort(a+1,a+m+1);
for(int i=1;i<=m;i++)Sum[i]=Sum[i-1]+a[i];//预处理前缀和
for(int i=1;i<=m;i++)dp[0][i]=inf;//DP边界
for(int i=1;i<=s;i++){//对每个i
head=tail=0;//清队列
q[tail++]=0;//加入决策0
for(int j=1;j<=m;j++){
while(head+1<tail&&f(i,q[tail-2],q[tail-1])>=f(i,q[tail-1],j))tail--;//维护队尾单调性
q[tail++]=j;//加入决策j
while(head+1<tail&&f(i,q[head],q[head+1])<=a[j])head++;//弹出过时决策
dp[i&1][j]=dp[i-1&1][q[head]]+a[j]*(j-q[head])-(Sum[j]-Sum[q[head]]);//计算DP值
}
}
cout<<dp[s&1][m];//目标
return 0;
}