【洛谷P5785】[SDOI2012]任务安排
题解
首先设计出 \(n^2\) 的 DP,\(dp(i,j)\) 表示前 \(i\) 个分成 \(j\) 段。
后来发现空间不够,于是第二维可以压掉,反正每一次都是从前一维状态转移来。
由于 \(T\) 的范围,新加入的斜率不一定单调递增,所以单调队列每次不能弹出队首元素,变成了单调栈。
仍然是维护斜率单调递增的下凸包,但是转移的时候二分找到最优的转移点。
代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int INF = 0x3f3f3f3f,N = 3e5+10;
inline ll read()
{
ll ret=0;char ch=' ',c=getchar();
while(!(c>='0'&&c<='9')) ch=c,c=getchar();
while(c>='0'&&c<='9') ret=(ret<<1)+(ret<<3)+c-'0',c=getchar();
return ch=='-'?-ret:ret;
}
ll dp[N],tmp[N];
ll s,n;
ll sumT[N],sumC[N];
int q[N],l,r;
inline double X(int i){return sumC[i];}
inline double Y(int i){return dp[i]-sumC[i]*s;}
inline int find(double t)
{
int L=l,R=r;
if(l==r) return q[l];//这样特判比较简洁
while(L<R)
{
int mid=(L+R+1)>>1;
if((Y(q[mid])-Y(q[mid-1]))<=(X(q[mid])-X(q[mid-1]))*t) L=mid;
else R=mid-1;
}
return q[L];//注意二分的是q数组下标
}
int main()
{
n=read(),s=read();
for(int i=1;i<=n;i++)
{
sumT[i]=read(),sumT[i]+=sumT[i-1];
sumC[i]=read(),sumC[i]+=sumC[i-1];
}
memset(dp,0x3f,sizeof(dp));//和暴力一样的初始化
dp[0]=0;
l=1,r=1;
for(int i=1;i<=n;i++)
{
int j=find(sumT[i]);//二分找到转移点
dp[i]=Y(j)+sumT[i]*(sumC[i]-sumC[j])+sumC[n]*s;
while(l<r&&(X(i)-X(q[r-1]))*(Y(q[r])-Y(q[r-1]))>=(X(q[r])-X(q[r-1]))*(Y(i)-Y(q[r-1]))) r--;
q[++r]=i;
}
printf("%lld\n",dp[n]);
return 0;
}