P5017 摆渡车(斜率优化dp + 细节)

在这里插入图片描述
在这里插入图片描述


由于 t 并不大,考虑以 t 为状态进行 dp。(这题有 O ( n ∗ m ) O(n*m) O(nm) 的优秀dp做法,这里只为了练习斜率优化dp)

维护 cnt[i] 表示前 i 时刻的人数sum[i] 表示前 i 时刻所有人的下标之和
dp[i] 表示在第 i 时刻发车,所有人等车的最小时间,显然最后答案分布在 [ t m a x , t m a x + m ] [t_{max},t_{max} + m] [tmax,tmax+m]

列出转移方程:dp[i] = dp[j] + i * (cnt[i] - cnt[j]) - (sum[i] - sum[j]))

展开得到:dp[i] = dp[j] + i * cnt[i] - i * cnt[j] - sum[i] + sum[j],两边移项得到:
dp[j] + sum[j] = i * cnt[j] + dp[i] + i * cnt[i],出现了 i * cnt[j] ,且横坐标 cnt[j] 单调递增,可以用斜率优化。

由于斜率 i 也具有单调性,因此决策具有单调性,用单调队列维护一个下凸包,每次将 i - m 维护到队列中,就可以保证 下标差 >= m

什么时候维护下凸包:当横坐标单增,要求的是最小值时,维护下凸包,要求的是最大值时维护上凸包。若横坐标单减,则求最小值时维护的是上凸包,求最大值时维护的是下凸包(指在二维平面上的呈现)

有个坑点就是 t i t_i ti 可以为 0,不做处理的话,以 0 为转移点会出错,可以将时间全部 + 1。


代码:

#include<bits/stdc++.h>
using namespace std;
const int maxn = 5e6 + 500;
typedef long long ll;
const ll inf = 1e18;
int v[maxn],n,m,tot,N;
ll dp[maxn],sum[maxn];
int q[maxn],front,rear;
ll calc(int x,int y) {
    
    
	return dp[y] + 1ll * x * (v[x] - v[y]) - (sum[x] - sum[y]);
}
ll getY(int x) {
    
    
	return dp[x] + sum[x];
}
ll getX(int x) {
    
    
	return v[x];
}
int main() {
    
    
	scanf("%d%d",&n,&m);
	for (int i = 1,x; i <= n; i++) {
    
    
		scanf("%d",&x);
		x++;
		v[x]++;										//人数前缀和 
		sum[x] += x;
		N = max(N,x + m);
	}
	for (int i = 1; i <= N; i++) {
    
    
		sum[i] += sum[i - 1];		//时间前缀和 
		v[i] += v[i - 1];
	}
	for (int i = 0; i < m; i++)
		dp[i] = calc(i,0);
	for (int i = m; i <= N; i++) {
    
    
		while (front + 1 < rear && (getY(i - m) - getY(q[rear])) * (getX(q[rear]) - getX(q[rear - 1])) 
			<= (getY(q[rear]) - getY(q[rear - 1])) * (getX(i - m) - getX(q[rear])))
		rear--;	
		q[++rear] = i - m;
		while (front + 1 < rear && calc(i,q[front + 1]) >= calc(i,q[front + 2]))
			front++;
		dp[i] = calc(i,q[front + 1]);
	}
	ll ans = dp[N];
	for (int i = N - m; i <= N; i++)
		ans = min(ans,dp[i]);
	printf("%lld\n",ans);
	return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_41997978/article/details/104732284