题解-Cats Transport
有 \(n\) 个山丘,\(m\) 只猫子,\(p\) 只铲屎官。第 \(i-1\) 个山丘到第 \(i\) 个山丘的距离是 \(d_i\)。第 \(i\) 只猫子在山丘 \(h_i\) 玩 \(t_i\) 时间。每个铲屎官可以选择出发时间,然后从 \(1\) 号山丘一直不停地每秒一个单位走到 \(n\) 号山丘,领走路上已经玩完的猫。求每只猫都被领走的最小猫子等待时间和。
数据范围:\(2\le n\le 10^5\),\(1\le m\le 10^5\),\(1\le p\le 100\),\(1\le d_i\le 10^4\),\(1\le h_i\le n\),\(0\le t_i\le 10^9\)。
可以先预处理一下简化问题:
令 \(D_i=\sum_{j=2}^i d_j\)。
所以 \(1\) 号山丘到 \(i\) 号山丘距离为 \(D_i\)。
令 \(T_i=t_i-D_{h_i}\)。
所以可以视为所有猫咪都在 \(1\) 号山丘,玩完的时间是 \(T_i\)。
将每只猫咪按 \(T_i\) 排序,使 \(T_{i-1}\le T_i\)(每只猫子只需要 \(T_i\) 一个信息即可)。
所以题目就变成了将序列 \(T_i\) 分成 \(p\) 个子序列,使得每个数与它的子序列中最大的数的差的和最小。
这里的子序列不要求连续,但是很明显在 \(T_i\) 有序的情况下,连续更优。
不妨设第 \(a\) 个铲屎官领走了第 \(j+1\sim i\) 只猫子,即子序列中的最大数为 \(T_i\),可以如下 \(\texttt{dp}\):
设 \(f_{a,i}\) 表示到第 \(a\) 个铲屎官领到第 \(i\) 只猫子的最小等待时间和。
优化一下,令 \(s_i=\sum_{j=1}^i T_j\):
套个斜率优化维护 \(\texttt{dp}\) 最小值下凸壳模板即可。
时间复杂度 \(\Theta(n+m\log m+mp)\),空间复杂度 \(\Theta(n+mp)\)。
Code
#include <bits/stdc++.h>
using namespace std;
//Start
#define re register
#define il inline
#define mk make_pair
#define pb push_back
#define db double
#define lng long long
#define fi first
#define se second
const int inf=0x3f3f3f3f;
const lng INF=0x3f3f3f3f3f3f3f3f;
//Data
const int N=100000,P=100;
int n,m,p;
lng d[N+7],t[N+7],s[N+7];
//DP
lng f[P+7][N+7];
pair<int,int> lor[P+7];
int q[P+7][N+7];
#define l(x) lor[x].fi
#define r(x) lor[x].se
il db X(re int a,re int j){
return j;
}
il db Y(re int a,re int j){
return f[a][j]+s[j];
}
il db slope(re int a,re int k,re int t){
return (Y(a,k)-Y(a,t))/(X(a,k)-X(a,t));
}
il lng F(re int a,re int i,re int j){
return f[a-1][j]+t[i]*(i-j)-(s[i]-s[j]);
}
il lng DP(){
for(re int a=0;a<=p;a++){ //必须初始化
lor[a]=mk(1,0);
q[a][++r(a)]=0;
}
for(re int a=1;a<=p;a++){
for(re int i=1;i<=m;i++){
// printf("(%d,%d)\n",a,i);
while(l(a-1)<r(a-1)&&slope(a-1,q[a-1][l(a-1)],q[a-1][l(a-1)+1])<=t[i]) l(a-1)++;
//维护a-1队列递推
f[a][i]=F(a,i,q[a-1][l(a-1)]);
while(l(a)<r(a)&&slope(a,q[a][r(a)-1],q[a][r(a)])>=slope(a,q[a][r(a)],i)) r(a)--;
//维护a队列为递推a+1做准备
q[a][++r(a)]=i;
}
}
return f[p][m];
}
//Main
int main(){
scanf("%d%d%d",&n,&m,&p);
if(p>=m) return puts("0"),0;
for(re int i=2,x;i<=n;i++){
scanf("%d",&x);
d[i]=d[i-1]+x;
}
for(re int i=1,x,y;i<=m;i++){
scanf("%d%d",&x,&y);
t[i]=-d[x]+y;
}
sort(t+1,t+m+1);
for(re int i=1;i<=m;i++) s[i]=s[i-1]+t[i];
printf("%lld\n",DP());
return 0;
}
祝大家学习愉快!