洛谷又关发题解入口了((
洛谷题目页面传送门 & CodeForces题目页面传送门
一条直线上有(n)座山,从左往右编号依次为(1sim n),第(i-1,i)座山的距离为(d_i)个单位时间走的路程。有(m)只猫,第(i)只去第(h_i)座山玩到时刻(t_i)。有(s)个铲屎官,每个铲屎官可以从任意时刻出发,从第(1)座山往第(n)座山走一趟,中间不停,抱走所有的未被抱走的且玩完的猫。第(i)只猫的等待时间为它被抱走的时刻减去(t_i)。求最小的所有猫的总等待时间。
(ninleft[2,10^5 ight],minleft[1,10^5 ight],sin[1,100])。
考虑某铲屎官从时刻(x)出发,那么显然第(i)只猫可能被他抱走当且仅当(x+sumlimits_{j=2}^{h_i}d_jgeq t_i)。关于(i)的放一边,得(t_i-sumlimits_{j=2}^{h_i}d_jleq x)。令(a_i=t_i-sumlimits_{j=2}^{h_i}d_j)。若真的被此铲屎官抱走了,那么等待时间为(x-a_i)。
于是问题转化为了:有(m)个数,第(i)个数为(a_i)。要求给出一个数集(S(|S|=s)),(forall iin[1,m]),计算(S)中最小的(geq a_i)的数减去(a_i),答案为这些差的和。求最小的和。
考虑将(a)排序。那么很容易得出结论:“(S)中最小的(geq a_i)的数”是非严格单调递增的。于是减去的数们是由不超过(s)个严格单调递增的相等段组成的。
考虑DP。设(dp_{i,j})表示前(j)个数,用了(i)个相等段(可以为空)时的最小的差的和。边界:(dp_{0,j}=egin{cases}0&j=0\+infty&j>0end{cases}),目标:(dp_{s,m})。转移的话,考虑枚举倒数第二个相等段的右端点(倒数第一个相等段的右端点为(j)),由于(a)的单调性,最后一个相等段的值显然为(a_j)最优。考虑预处理出(a)的前缀和数组(Sum),那么状态转移方程很容易列出:(dp_{i,j}=minlimits_{k=0}^j{dp_{i-1,k}+a_j(j-k)-(Sum_j-Sum_k)})。
直接转移(mathrm O!left(m^2s ight))。这一看就是个斜率优化的模型,考虑对每个(i)做一次斜率优化:对于(2)个决策(k<o),(k)比(o)优当且仅当
变形后,由(k<o)得(k-o<0),除过去不等号变方向,得
再基础不过了根据(a)的单调性,单调队列维护下凸壳即可。
时间复杂度(mathrm O(n+mlog 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;
}