【DP计划】11.2——[CF]CF311B(斜率优化) EASY+

本文探讨了一个关于猫回收的问题,通过将问题转化为所有猫在第一座山不同时间结束玩耍的场景,使用动态规划和斜率优化技术,实现了对饲养员出发时间的最优规划,以最小化所有猫等待时间的总和。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

小 S 是农场主,他养了 MMM 只猫,雇了 PPP 位饲养员。农场中有一条笔直的路,路边有 NNN 座山,从 111NNN 编号。第 iii 座山与第 i−1i-1i1 座山之间的距离是 DiD_iDi​​ 。饲养员都住在 111 号山上。

有一天,猫出去玩。第 iii 只猫去 HiH_iHi号山玩,玩到时刻 TiT_iTi停止,然后在原地等饲养员来接。饲养员们必须回收所有的猫。每个饲养员沿着路从 111 号山走到 NNN 号山,把各座山上已经在等待的猫全部接走。饲养员在路上行走需要时间,速度为 111 米每单位时间。饲养员在每座山上接猫的时间可以忽略,可以携带的猫的数量为无穷大。

例如有两座相距为 111 的山,一只猫在 222 号山玩,玩到时刻 333 开始等待。如果饲养员从 111 号山在时刻 222333 出发,那么他可以接到猫,猫的等待时间为 000111。而如果他于时刻 111 出发,那么他将于时刻 222 经过 222 号山,不能接到当时仍在玩的猫。

你的任务是规划每个饲养员从 111 号山出发的时间,使得所有猫等待时间的总和尽量小。饲养员出发的时间可以为负。
输入格式
第一行三个整数 N,M,PN,M,PN,M,P
第二行 N−1N-1N1 个正整数 DiD_iDi​​ ,表示第 iii 座山与第 i−1i-1i1 座山之间的距离是 DiD_iDi
接下去 MMM 行每行两个整数 Hi,TiH_i,T_iHi,Ti
输出格式
输出一个整数表示答案。
样例
样例输入

4 6 2
1 3 5
1 0
2 1
4 9
1 10
2 10
3 12
样例输出

3
数据范围与提示
对于全部数据,2≤N≤105,1≤M≤105,1≤p≤100,1≤Di&lt;104,1≤Hi≤N,0≤Ti≤1092\le N\le 10^5,1\le M\le 10^5,1\le p\le 100,1\le D_i\lt 10^4,1\le H_i\le N,0\le T_i\le 10^92N105,1M105,1p100,1Di<104,1HiN,0Ti109


我们可以先做一个处理(dis[i]表示从第1座山到第i座山的距离)
	for(int i=1;i<=m;i++){
		F[i].p=read();F[i].num=read()-dis[F[i].p];
	}
因为在第iii座山玩到时刻TTT和在第一座山玩到时刻T−dis[i]T-dis[i]Tdis[i]是一样的(dis[i]dis[i]dis[i]就是管理员要走到的时间)
这样就变成所有猫都在第一座山,在不同的时间结束。总共P个管理员,当管理员iii在时间点TiT_iTi,上一个管理员在时间点TlastT_{last}Tlast时,那么[Tlast,Ti][T_{last},T_i][Tlast,Ti]之间的猫都由当前管理员iii带走。所以我们就可以轻松的写出方程:
f[i][j]=min(f[i][j],f[p][j-1]+F[i].num*(i-p)-(sum[i]-sum[p]))
//f[p][j-1]右边这一串就表示当前区间内猫的等待时间,因为每只猫等的时间为F[i].num-F[x].num
//F[i].num代表管理员出发的时间(显然与当前区间的最后一直猫同时最优),将这个式子求个Σ,用前缀和记录,就是上面的DP
那么一顿斜率操作之后(具体的操作可以看我之前的博客)就变成了
f[i][j]+F[i].num*p=f[p][j-1]+F[i].num*i-sum[i]+sum[p]
b    +      k     *x=                                 y
接下来用单调队列维护一个下凸包即可。
#include<bits/stdc++.h>
#define MAXN 100005
#define db double
#define ll long long
using namespace std;
ll read(){
	char c;ll x;while(c=getchar(),c<'0'||c>'9');x=c-'0';
	while(c=getchar(),c>='0'&&c<='9') x=x*10+c-'0';return x;
}
ll n,m,p,w,r,d[MAXN],dis[MAXN],sum[MAXN],f[MAXN][105],q[MAXN];
struct node{
	ll p,num;
	bool operator<(const node x)const{
		return num<x.num;
	}
}F[MAXN];
db x(ll i){return i;}
db y(ll i,ll p){return f[i][p]+sum[i];}
db k(ll i,ll j,ll p){
	if(x(i)==x(j)) return 2e9;
	return (y(i,p-1)-y(j,p-1))/(x(i)-x(j));
}
int main()
{
	n=read();m=read();p=read();
	for(int i=2;i<=n;i++) d[i]=read(),dis[i]=dis[i-1]+d[i];
	for(int i=1;i<=m;i++){
		F[i].p=read();F[i].num=read()-dis[F[i].p];
	}
	sort(F+1,F+1+m);
	for(int i=1;i<=m;i++)sum[i]=sum[i-1]+F[i].num;
	memset(f,127,sizeof(f));f[0][0]=0;
	for(int j=1;j<=p;j++){
		w=r=0;
		for(int i=1;i<=m;i++){
			while(r<w&&F[i].num>k(q[r],q[r+1],j)) r++;
			f[i][j]=f[q[r]][j-1]+F[i].num*i-F[i].num*q[r]-sum[i]+sum[q[r]];
			while(r<w&&k(q[w-1],q[w],j)>k(q[w],i,j)) w--;
			q[++w]=i;
		}
	}
	printf("%lld",f[m][p]);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值