Ccodeforces311B cats Transport 【斜率优化 dp】

传送门

描述

小 S 是农场主,他养了 M 只猫,雇了 P 位饲养员。农场中有一条笔直的路,路边有 N 座山,从 1 到 N 编号。第 i 座山与第 i−1 座山之间的距离是 Di 。饲养员都住在 1 号山上。

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

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

你的任务是规划每个饲养员从 1 号山出发的时间,使得所有猫等待时间的总和尽量小。饲养员出发的时间可以为负。

输入
第一行三个整数 N,M,P

第二行 N−1 个正整数 Di ,表示第 i 座山与第 i−1座山之间的距离是 Di

接下去 M行每行两个整数 Hi,Ti

输出
输出一个整数表示答案。

样例输入 [复制]
4 6 2
1 3 5
1 0
2 1
4 9
1 10
2 10
3 12
样例输出 [复制]
3

数据范围:
对于全部数据,2≤N≤1e5,1≤M≤1e5,1≤p≤100,1≤Di<1e4,1≤Hi≤N,0≤Ti≤1e9


SOL

简化模型很重要。。。

模拟 样例,发现对于 一部分由一个人一次接的喵,相当于分了一个组。。
我们即需要知道如何 “分组”
约束很多,我们可以尝试 “消元”;

对于第 i 只喵, 只要出发时间 t >= Ti- (H[1]+…+H[i]) 就可以被接到。
显然,选择一个很晚的时间出发可以接到所有喵

令 a [i] = Ti- (H[1]+…+H[i])

等待时间(代价) : t- a[i]

这时有一个显然的贪心:
将 a 升序排序 , 选择相邻的 a ,出发时间 :t= a(max)
这肯定是 局部最优的

问题变成了 有一个升序的序列 a, 分成 p 个区间 ,每个区间的代价是 区间每一个数和区间最大值的差值的绝对值之和 使总代价最小

dp方程就显然了 :(s[i]是a数组前缀和
dp[i][k]=min(dp[j][k-1] +(i-j)*a[i]-(s[i]-s[j]))

之后是一个基本单调队列操作。

个人小结:
1.斜率比较的时候,如果交叉相乘要注意变号。。。。

2.队列里维护的不一定是当前维度的答案。本题就是维护上一维度的答案。本质:在备选决策中优化选择时间 。对象:备选决策。

3.注意0在本题的状态定义下是有实际含义的,队列里面一开始初始化是有0这样一个备选决策 (前缀和 s[i]-s[j] 0<=j<=i-1)
dp[0][0]=0,而dp[k][0]不存在,初始化成 INF

4.哪位大佬比较一下 斜率用交叉相乘好一些还是直接除 (感觉直接除更直观,不容易写挂。。)


SOL

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int maxn=1e5+10;
int dp[maxn][150],s[maxn],a[maxn];
int h[maxn];
int n,m,P;
int q[maxn];
inline int Y(int i,int k){
	return dp[i][k-1]+s[i];
}
signed main(){
	scanf("%d%d%d",&n,&m,&P);
	for(int i=2;i<=n;++i)scanf("%d",&h[i]),h[i]+=h[i-1];
	for(int i=1;i<=m;++i){
		int x,y;scanf("%d%d",&x,&y);
		a[i]=y-h[x];
	}
	sort(a+1,a+m+1);
	for(int i=1;i<=m;++i)s[i]=s[i-1]+a[i];
	memset(dp,0x5f,sizeof dp);
	dp[0][0]=0;
	for(int k=1;k<=P;++k){
		int head=1,tail=1;
		for(int i=1;i<=m;++i){
			while(head<tail&&(Y(q[head+1],k)-Y(q[head],k))<=(q[head+1]-q[head])*a[i])++head;
			int j=q[head];
			dp[i][k]=dp[j][k-1]+(i-j)*a[i]-s[i]+s[j];
//			cerr<<j<<endl;
//			cerr<<i<<" "<<k<<" "<<dp[i][k]<<endl;
			
			while(head<tail&&(Y(q[tail-1],k)-Y(q[tail],k))*(q[tail-1]-i)>=(Y(q[tail-1],k)-Y(i,k))*(q[tail-1]-q[tail]))--tail;
			q[++tail]=i;
		}
	}
	printf("%lld",dp[m][P]);
	return 0;
}
 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值