Luogu P2365 任务安排

题目链接:Click here

Solution:

我们设(f[i])表示我们把(i)单独划分出来后的对总答案的最小代价,(c[i])表示花费的前缀和,(t[i])表示时间的前缀和

[f[i]=f[j]+t[i] imes (c[i]-c[j])+S imes (c[n]-c[j])\ f[i]=f[j]+t[i] imes c[i]-t[i] imes c[j]+S imes c[n]-S imes c[j]\ -f[j]=t[i] imes c[i]-t[i] imes c[j]+S imes c[n]-S imes c[j]-f[i]\ -f[j]=(-t[i]-S) imes c[j]+t[i] imes c[i]+S imes c[n]-f[i]\ f[j]=(t[i]+S) imes c[j]-t[i] imes c[i]-S imes c[n]+f[i]\ ]

我们把这个式子看作形如:(y=kx+b)的一次函数,(c[j])看作自变量,则式子的斜率是确定的,截距只有(f[i])不确定

则我们想让截距(b)尽量的小,则我们需要用将这个斜率确定的直线向上平移碰到的第一个点(二元组((c[j],f[j])))来更新

因为这样我们得到的截距才会是最小的,那么我们只要维护一个下凸包就好了

为什么是下凸包呢?考虑选取下凸包中连续三个点(a_1,a_2,a_3),由于他是一个下凸包,我们可以得出(k(a_2,a_1)<k(a_3,a_2))

其中(k(a,b))表示(a,b)两点连线的斜率。当一个点能更新当前状态时,它一定是下凸包上的点,一个向上凸的点是不可能转移的(可以画图理解)

同时我们注意到它的斜率是单调递增的,所以我们可以维护队首为答案

Code:

#include<bits/stdc++.h>
using namespace std;
const int N=5e3+1;
int n,s,t[N],c[N],f[N];
int head,tail,q[N];
int read(){
	int x=0,f=1;char ch=getchar();
	while(!isdigit(ch)){if(ch=='-')f=-f;ch=getchar();}
	while(isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
int main(){
	n=read();s=read();
	for(int i=1;i<=n;i++){
		t[i]=read(),c[i]=read();
		t[i]+=t[i-1],c[i]+=c[i-1];
    }
	for(int i=1;i<=n;i++){
		while(head<tail&&(f[q[head+1]]-f[q[head]]<=(t[i]+s)*(c[q[head+1]]-c[q[head]]))) ++head;
		f[i]=f[q[head]]+t[i]*(c[i]-c[q[head]])+s*(c[n]-c[q[head]]);
		while(head<tail&&(f[i]-f[q[tail]])*(c[q[tail]]-c[q[tail-1]])<=(f[q[tail]]-f[q[tail-1]])*(c[i]-c[q[tail]])) --tail;
		q[++tail]=i;
	}printf("%d
",f[n]);
	return 0;
}

原文地址:https://www.cnblogs.com/NLDQY/p/11264904.html