P2120
题目传送门
这是道非常经典的斜率优化
感觉非常适合像我一样的初学者练习
Solution
dp,首先定义状态
\(dp_{i,j}\)表示在i位置是选择建仓库(0)还是向下运(1)
这样容易写出转移方程式
\(dp_{i,1}=min(dp_{i,1},dp_{j,0}+sum_i-sum_j-(x_i-x_j)*p_j)\)
\(dp_{i,0}=min(dp_{i-1,0},dp_{i,1})+c_i\)
提交
看看数据范围\(1 \leq n \leq 1e6~O(N^2)\)明显TLE
怎么办呢
当然是斜率优化
将转移方程式化简一下
\(dp_{j,0}-sum_j+x_j*p_j=x_i*p_j+dp_{i,1}-sum_i\)
将\(dp_{j,0}-sum_j+x_j*p_j=y\) \(p_j\)为\(x\) 剩下那一堆为\(b\)
那么就可以轻松求解斜率啦
最后用单调队列维护下凸壳
这样就将时间复杂度降到\(O(N)\)
上代码(含有注释)
#include<bits/stdc++.h>
#define int long long
#define double long double//避免精度下降
using namespace std;
const int N = 1e6+5;
int n,x[N],p[N],c[N];
int q[N];
//工厂 i 距离工厂 11 的距离 x
//工厂 i 目前已有成品数量 p
int sum[N];
int dp[N][2];//0 buld 1 move
inline double solve(int i,int j)
{
//dp[j][0]-sum[j]+x[j]*p[j]=dp[i][1]-sum[i]+x[i]*p[j]
int y1=dp[i][0]-sum[i]+x[i]*p[i];
int y2=dp[j][0]-sum[j]+x[j]*p[j];
int x1=p[i],x2=p[j];
return 1.0*(y1-y2)/(x1-x2);
//求解斜率
}
inline int read()
{
int s=0,w=1;
char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-') w=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){s=s*10+ch-'0';ch=getchar();}
return s*w;
}// 日常快读
signed main()
{
n=read();
for(register int i=1;i<=n;i++)
{
x[i]=read(),p[i]=read(),c[i]=read();
p[i]+=p[i-1];
sum[i]=sum[i-1]+(x[i]-x[i-1])*(p[i-1]);
//将所有东西运到i的总花费(不考虑建造仓库)
}
int head=1,tail=1;
for(register int i=1;i<=n;i++)
{
/*
dp[i][1]=sum[i];//全部运
for(register int j=1;j<i;j++)//在j处建造仓库
{
dp[i][1]=min(dp[i][1],dp[j][0]+sum[i]-sum[j]-(x[i]-x[j])*(p[j]));
//全部运到i-全部运到j==将全部从j运到i(多算x[j]
}
dp[i][0]=min(dp[i-1][0],dp[i][1])+c[i];
*/
//dp[i][1]-sum[i]=-x[i]*p[j]+dp[j][0]-sum[j]+x[j]*p[j]
//dp[j][0]-sum[j]+x[j]*p[j]=x[i]*p[j]+dp[i][1]-sum[i]
while(head<tail&&solve(q[head+1],q[head])<x[i]) head++;
// 若最小斜率小于与直线相切的斜率 入队
int j=q[head];
dp[i][1]=dp[j][0]+sum[i]-sum[j]-(x[i]-x[j])*p[j];
dp[i][0]=min(dp[i-1][0],dp[i][1])+c[i];
while(head<tail&&solve(i,q[tail])<solve(q[tail],q[tail-1])) tail--;
q[++tail]=i;
// 出队
}
printf("%d",min(dp[n][1]+c[n],dp[n][0]));
//最终答案
return 0;
}
// 完美AC
// 70行
AC这道题后,建议去看看玩具装箱

浙公网安备 33010602011771号