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\)优当且仅当

\[dp_{i-1,k}+a_j(j-k)-(Sum_j-Sum_k)<dp_{i-1,o}+a_j(j-o)-(Sum_j-Sum_o) \]

变形后,由\(k<o\)\(k-o<0\),除过去不等号变方向,得

\[f(k,o)=\frac{(dp_{i-1,k}+Sum_k)-(dp_{i-1,o}+Sum_o)}{k-o}>a_j \]

再基础不过了根据\(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;
}
posted @ 2020-05-26 19:53  ycx060617  阅读(132)  评论(0编辑  收藏  举报