https://www.luogu.com.cn/problem/P4360
做两遍dp,dp[k][i]表示前i个位置放k个锯木厂的最小值
先把整个顺序反过来,那么0的位置就是山脚的锯木厂
设sum[i],f[i]是sum[i]w[i]前缀和,f2[i]是w[i]的前缀和
dp[1][i]=f[i]-f[k]-sum[k+1](f2[i]-f2[k])+dp[0][k]
展开成f2[k]sum[k+1]-f[k]+dp[0][k]=f2[i]sum[k+1]+dp[1][i]-f[i]
k=f2[i]递增,sum[k+1]递增,希望截距dp[1][i]-f[i]尽可能小
画图分析得用单调队列维护一个下凸包
#include<bits/stdc++.h>
using namespace std;
const int maxl=2e4+10;
typedef long long ll;
int n;
ll w[maxl],d[maxl],sum[maxl],f[maxl],f2[maxl];
ll dp[3][maxl];
struct node
{
ll x,y;
}s[maxl];
inline void prework()
{
scanf("%d",&n);
for(int i=n;i>=1;i--)
scanf("%lld%lld",&w[i],&d[i]);
for(int i=1;i<=n;i++)
{
sum[i]=sum[i-1]+d[i];
f[i]=f[i-1]+w[i]*sum[i];
f2[i]=f2[i-1]+w[i];
dp[0][i]=f[i];
}
}
inline ll calc(ll k,int id)
{
return s[id].y-k*s[id].x;
}
inline bool cmpk(node a,node b,node c)
{
return 1.0*(b.y-a.y)*(c.x-b.x)>=1.0*(c.y-b.y)*(b.x-a.x);
}
inline void solv(int k)
{
int head=1,tail=0;
node d=node{sum[1],0};
s[++tail]=d;
for(int i=1;i<=n;i++)
{
while(head<tail && calc(f2[i],head)>=calc(f2[i],head+1))
head++;
dp[k][i]=calc(f2[i],head)+f[i];
d=node{sum[i+1],f2[i]*sum[i+1]-f[i]+dp[k-1][i]};
while(head<tail && cmpk(s[tail-1],s[tail],d))
tail--;
s[++tail]=d;
}
}
inline void mainwork()
{
for(int k=1;k<=2;k++)
solv(k);
}
inline void print()
{
printf("%lld",dp[2][n]);
}
int main()
{
prework();
mainwork();
print();
return 0;
}
来源:CSDN
作者:二分抄代码
链接:https://blog.csdn.net/liufengwei1/article/details/104431678