这题代码调了我好久。。。
转移方程的推导
设 表示完成前
个任务的最小花费。
我们可以很容易得到转移方程:
其中 ,
为任务花费的前缀和,
为完成任务所需时间的前缀和。
但是这样的时间复杂度是 的,那是会超时的好吧。
所以我们得考虑该怎么优化。
DP 优化的过程
将上面的转移方程展开(可以不管取 )
把 看作常数,把
看作未知数,将只含
的式子移到等号左边,其余移项到等号右边。
聪明的你通过手算后可以得到这个式子:
然后你就会发现 这个项既与
有关又与
有关,所以这是不能用单调队列优化的。
但是聪明的你会看着这个式子有点眼熟。
觉得它有点像
所以,我们可以考虑斜率优化。
在这道题中,
注意:
在这道题目中,,所以我们的斜率
不是递增的,所以我们就用二分查找凸包上的决策点即可。
然后直接套斜率优化的模板即可。
代码
如果直接算斜率可能会有精度丢失,所以这里是交叉相乘比较大小。
#include<bits/stdc++.h>
using namespace std;
#define int long long
int n,s;
int t[300001],c[300001];
int a[300001],b[300001];
int f[300001];
int q[300001];
int dx(int i,int j)
{
int x1=c[i];
int x2=c[j];
return x1-x2;
}
int dy(int i,int j)
{
int y1=f[i]-c[i]*s;
int y2=f[j]-c[j]*s;
return y1-y2;
}
int head=1,tail=0;
int erfen(int x)
{
if(head==tail)return q[head];
int l=head,r=tail,ans;
while(l<=r)
{
int mid=(l+r)>>1;
if(dy(q[mid],q[mid-1])<=t[x]*dx(q[mid],q[mid-1]))
ans=mid,l=mid+1;
else
r=mid-1;
}
return q[ans];
}
main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin>>n>>s;
for(int i=1;i<=n;i++)
{
cin>>a[i]>>b[i];
t[i]=t[i-1]+a[i];
c[i]=c[i-1]+b[i];
}
for(int i=1;i<=n;i++)
{
while(head<tail&&dy(q[tail],q[tail-1])*dx(i-1,q[tail])>=dx(q[tail],q[tail-1])*dy(i-1,q[tail]))
{
tail--;
}
q[++tail]=i-1;
int j=erfen(i);
f[i]=f[j]+(c[i]-c[j])*t[i]+(c[n]-c[j])*s;
}
cout<<f[n]<<endl;
return 0;
}