总结:
如果dp方程写出来之后大概是长这样的 f [ i ] = ∑ 0 < j < i m i n ( f [ j ] + s [ i , j ] ) + … f[i]=\sum_{0<j<i} min(f[j]+s[i,j])+… f[i]=∑0<j<imin(f[j]+s[i,j])+…,就可以考虑斜率优化(关于斜率: y [ i ] − y [ j ] x [ i ] − x [ j ] \frac{y[i]-y[j]}{x[i]-x[j]} x[i]−x[j]y[i]−y[j])。先设 k 1 k_1 k1< k 2 k2 k2,分析一下是否有 f [ k 1 ] f[k_1] f[k1]< f [ k 2 ] f[k2] f[k2](证明决策单调性),然后大力推一发柿子(通常是把关于 j 1 j1 j1 j 2 j2 j2的项移到坐标,关于i的项移到右边),找到 x x x坐标和 y y y坐标所对应的值,用单调队列维护有用的决策,就可以乱搞ac了
loj#10184. 「一本通 5.6 例 1」任务安排 1
这题应该是先教你搞出dp公式
注意题意,执行任务的时间是同一批任务的所用时间总和+启动时间,费用算的是每一个任务所属批次的结束时间*费用系数
设
t
[
i
]
t[i]
t[i]为时间的前缀和,
c
[
i
]
c[i]
c[i]为费用的前缀和,$
f
[
i
]
f[i]
f[i]为执行1~i 的最少花费,枚举当前批次的任务为 j ~ i,显然有当前当前批次的费用
(
t
[
i
]
+
s
)
∗
(
c
[
i
]
−
c
[
j
]
)
(t[i]+s)*(c[i]-c[j])
(t[i]+s)∗(c[i]−c[j]),机器的启动时间S会对后面的所有任务的完成时间都产生一个大小为S的后延,补充到当前的费用中,此时
f
[
i
]
=
∑
1
<
=
j
<
i
m
i
n
(
f
[
j
]
+
(
t
[
i
]
+
S
)
∗
(
c
[
i
]
−
c
[
j
]
)
+
(
c
[
n
]
−
c
[
i
]
)
∗
S
)
f[i]=\sum_{1<=j<i} min(f[j]+(t[i]+S)*(c[i]-c[j])+(c[n]-c[i])*S)
f[i]=∑1<=j<imin(f[j]+(t[i]+S)∗(c[i]−c[j])+(c[n]−c[i])∗S),这题的数据范围
1
≤
N
≤
5000
1≤N≤5000
1≤N≤5000,可以放心
N
2
N^2
N2 dp
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
int list[5100];
long long f[5100],t[5100],c[5100];
int main()
{
int n,S;
scanf("%d%d",&n,&S);
for (int i=1;i<=n;i++)
{
long long tt,cc;
scanf("%lld%lld",&tt,&cc);
t[i]=t[i-1]+tt;
c[i]=c[i-1]+cc;
}
memset(f,63,sizeof(f));
f[0]=0;
for (int i=1;i<=n;i++)
{
for (int j=0;j<i;j++)
{
f[i]=min(f[j]+(t[i]+S)*(c[i]-c[j])+(c[n]-c[i])*S,f[i]);
}
}
printf("%lld\n",f[n]);
return 0;
}
loj#10185. 「一本通 5.6 例 2」任务安排 2
(事实证明用t1的
N
2
N^2
N2做法是能过的)
有
j
1
<
j
2
j1<j2
j1<j2,试证明
j
2
j2
j2的决策比
j
1
j1
j1优秀
不想证,还是去看进阶指南吧
f
[
j
1
]
+
(
t
[
i
]
+
S
)
∗
(
c
[
i
]
−
c
[
j
1
]
)
+
(
c
[
n
]
−
c
[
i
]
)
∗
S
<
f
[
j
1
]
+
(
t
[
i
]
+
S
)
∗
(
c
[
i
]
−
c
[
j
1
]
)
+
(
c
[
n
]
−
c
[
i
]
)
∗
S
f[j1]+(t[i]+S)*(c[i]-c[j1])+(c[n]-c[i])*S<f[j1]+(t[i]+S)*(c[i]-c[j1])+(c[n]-c[i])*S
f[j1]+(t[i]+S)∗(c[i]−c[j1])+(c[n]−c[i])∗S<f[j1]+(t[i]+S)∗(c[i]−c[j1])+(c[n]−c[i])∗S
拆括号,抵消,移项,得
f
[
j
1
]
−
S
∗
c
[
j
1
]
−
f
[
j
2
]
+
S
∗
c
[
j
2
]
<
t
[
i
]
∗
c
[
j
2
]
−
t
[
i
]
∗
c
[
j
1
]
f[j1]-S*c[j1]-f[j2]+S*c[j2]<t[i]*c[j2]-t[i]*c[j1]
f[j1]−S∗c[j1]−f[j2]+S∗c[j2]<t[i]∗c[j2]−t[i]∗c[j1]
右边只保留t[i]的项,
(
f
[
j
1
]
−
S
∗
c
[
j
1
]
)
−
(
f
[
j
2
]
−
S
∗
c
[
j
2
]
)
−
c
[
j
1
]
+
c
[
j
2
]
<
t
[
i
]
\frac{(f[j1]-S*c[j1])-(f[j2]-S*c[j2])}{-c[j1]+c[j2]}<t[i]
−c[j1]+c[j2](f[j1]−S∗c[j1])−(f[j2]−S∗c[j2])<t[i]
得到公式啦!
根据“及时排出无用决策”,本题应是要维护一个下凸壳
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
int list[11000];
long long f[11000],t[11000],c[11000];
int n,S;
int X(int i)
{
return c[i];
}
int Y(int i)
{
return f[i]-S*c[i];
}
double slop(int j1,int j2)
{
return double(Y(j2)-Y(j1))/double(X(j2)-X(j1));
}
int main()
{
scanf("%d%d",&n,&S);
for (int i=1;i<=n;i++)
{
long long tt,cc;
scanf("%lld%lld",&tt,&cc);
t[i]=t[i-1]+tt;
c[i]=c[i-1]+cc;
}
int head=1,tail=1;
for (int i=1;i<=n;i++)
{
while (head+1<=tail&&slop(list[head],list[head+1])<t[i]) head++;
f[i]=f[list[head]]+(t[i]+S)*(c[i]-c[list[head]])+(c[n]-c[i])*S;
while (head<=tail-1&&slop(list[tail],i)<slop(list[tail-1],list[tail])) tail--;
list[++tail]=i;
}
printf("%lld\n",f[n]);
return 0;
}
loj#10186. 「一本通 5.6 例 3」任务安排 3
https://loj.ac/problem/10186
时间可能为负数,因此时间的前缀和t[i]也不具有单调性
而我们要维护斜率还是具有单调性的!!
结束删队头的操作,最后出来的list[head]左边的斜率小于t[i],右边的斜率大于t[i]
所以通过 二分查找到 左边的斜率小于t[i],右边的斜率大于t[i]的点
!!sd模版骗我钱财!! 数字太大还是要把除移项变成乘好一点
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long LL;
int list[310000];
LL f[310000],t[310000],c[310000];
int n,S;
LL X(int i)
{
return c[i];
}
LL Y(int i)
{
return f[i]-S*c[i];
}
double slop(int j1,int j2)
{
return double(Y(j2)-Y(j1))/double(X(j2)-X(j1));
}
int main()
{
scanf("%d%d",&n,&S);
for (int i=1;i<=n;i++)
{
int tt,cc;
scanf("%d%d",&tt,&cc);
t[i]=t[i-1]+tt;
c[i]=c[i-1]+cc;
}
int head=1,tail=1; list[1]=0;
for (int i=1;i<=n;i++)
{
//while (head+1<=tail&&slop(list[head],list[head+1])<t[i]) head++;
int l=head,r=tail;
while (l<r)
{
int mid=(l+r)/2;
if (f[list[mid]]-f[list[mid+1]]>=(S+t[i])*(c[list[mid]]-c[list[mid+1]])) l=mid+1; else r=mid;
}
f[i]=f[list[l]]-(S+t[i])*c[list[l]]+t[i]*c[i]+S*c[n];
while (head<=tail-1&&(f[list[tail]]-f[list[tail-1]])*(c[i]-c[list[tail]])>=(f[i]-f[list[tail]])*(c[list[tail]]-c[list[tail-1]])) tail--;
list[++tail]=i;
}
printf("%lld\n",f[n]);
return 0;
}
loj#10187. 「一本通 5.6 例 4」Cats Transport
https://loj.ac/problem/10187
d
[
i
]
d[i]
d[i]为d的前缀和,即1号山到i号山的距离
如果想要接到第i只猫,就得在
t
[
i
]
−
d
[
h
[
i
]
]
t[i]-d[h[i]]
t[i]−d[h[i]]时或之后出发,才能在猫玩够之后到达
令
a
[
i
]
=
t
[
i
]
−
d
[
h
[
i
]
]
a[i]=t[i]-d[h[i]]
a[i]=t[i]−d[h[i]],对a排序,根据贪心策略,一个饲养员接到的猫一定是a[]排序后连续的一段,那么此时问题又回到了任务安排2了
设
s
[
i
]
s[i]
s[i]为a[ ]的前缀和,f[i][j]为前i个饲养员,接到前j只猫的最小等待时间
有dp方程
f
[
i
]
[
j
]
=
m
i
n
(
f
[
i
−
1
]
[
k
]
−
(
s
[
j
]
−
s
[
k
]
)
+
a
[
j
]
∗
(
j
−
k
)
)
f[i][j]=min(f[i-1][k]-(s[j]-s[k])+a[j]*(j-k))
f[i][j]=min(f[i−1][k]−(s[j]−s[k])+a[j]∗(j−k))
设
k
1
<
k
2
k1<k2
k1<k2,因为时间,路程均为正整数,一定有k2优于k1
f
[
i
−
1
]
[
k
1
]
−
(
s
[
j
]
−
s
[
k
1
]
)
+
a
[
j
]
∗
(
j
−
k
1
)
>
f
[
i
−
1
]
[
k
2
]
−
(
s
[
j
]
−
s
[
k
2
]
)
+
a
[
j
]
∗
(
j
−
k
2
)
f[i-1][k1]-(s[j]-s[k1])+a[j]*(j-k1)>f[i-1][k2]-(s[j]-s[k2])+a[j]*(j-k2)
f[i−1][k1]−(s[j]−s[k1])+a[j]∗(j−k1)>f[i−1][k2]−(s[j]−s[k2])+a[j]∗(j−k2)
拆括号,抵消,移项,注意
k
1
−
k
2
<
0
k1-k2<0
k1−k2<0,得
(
f
[
i
−
1
]
[
k
1
]
−
s
[
k
1
]
)
−
(
a
[
i
−
1
]
[
k
2
]
−
s
[
k
2
]
)
k
1
−
k
2
<
a
[
j
]
\frac{(f[i-1][k1]-s[k1])-(a[i-1][k2]-s[k2])}{k1-k2}<a[j]
k1−k2(f[i−1][k1]−s[k1])−(a[i−1][k2]−s[k2])<a[j]
上一题血的教训告诉我写代码还是把它弄成乘法吧
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define LL long long
LL d[110000],a[110000],s[110000],list[110000];
LL f[110][110000];//f[饲养员i][收回前j只猫]
bool cmp(LL x,LL y) {return x<y;}
LL Y(LL i,int k)
{
return f[i][k]+s[k];
}
LL X(LL k)
{
return k;
}
int main()
{
int n,m,p;
scanf("%d%d%d",&n,&m,&p);
for (int i=2;i<=n;i++) {scanf("%d",&d[i]);d[i]+=d[i-1];}
for (int i=1;i<=m;i++)
{
int h,t;
scanf("%d%d",&h,&t);
a[i]=t-d[h];
}
sort(a+1,a+1+m,cmp);
for (int i=1;i<=m;i++) s[i]=s[i-1]+a[i];
memset(f,63,sizeof(f));
for (int i=0;i<=p;i++) f[i][0]=0;
for (int i=1;i<=p;i++)
{
int head=1,tail=1; list[1]=0;
for (int j=1;j<=m;j++)
{
while (head+1<=tail&& (Y(i-1,list[head])-Y(i-1,list[head+1]))>=a[j]*(list[head]-list[head+1])) head++;
f[i][j]=min(f[i-1][list[head]]+a[j]*(j-list[head])-(s[j]-s[list[head]]),f[i-1][j]);
if (f[i-1][j]+s[j]>=455743088879883099) continue;
while (head<=tail-1&& (Y(i-1,list[tail-1]) - Y(i-1,list[tail])) * (list[tail]-j) > (Y(i-1,list[tail])-Y(i-1,j))*(list[tail-1]-list[tail])) tail--;
list[++tail]=j;
}
}
printf("%lld\n",f[p][m]);
return 0;
}
loj#10188. 「一本通 5.6 练习 1」玩具装箱
https://loj.ac/problem/10188
sum[i]为玩具长度的前缀和
显然有
f
[
i
]
=
∑
1
≤
j
<
i
m
i
n
(
f
[
j
]
+
(
s
u
m
[
i
]
−
s
u
m
[
j
]
+
i
−
j
−
1
−
L
)
2
)
f[i]=\sum_{1≤j<i}{min(f[j]+(sum[i]-sum[j]+i-j-1-L)^2)}
f[i]=∑1≤j<imin(f[j]+(sum[i]−sum[j]+i−j−1−L)2)
令c[i]=sum[i]+i,L=1+L
dp方程:
f
[
i
]
=
m
i
n
(
f
[
j
]
+
(
c
[
i
]
−
c
[
j
]
−
L
)
2
)
f[i]=min(f[j]+(c[i]-c[j]-L)^2)
f[i]=min(f[j]+(c[i]−c[j]−L)2)
设
k
1
<
k
2
k1<k2
k1<k2 (我喜欢用k)
f
[
k
1
]
+
(
c
[
i
]
−
c
[
k
1
]
−
L
)
2
<
f
[
k
2
]
+
(
c
[
i
]
−
c
[
k
2
]
−
L
)
2
f[k1]+(c[i]-c[k1]-L)^2<f[k2]+(c[i]-c[k2]-L)^2
f[k1]+(c[i]−c[k1]−L)2<f[k2]+(c[i]−c[k2]−L)2
把
(
c
[
i
]
−
L
)
(c[i]-L)
(c[i]−L)看作一个整体,完全平方公式来一发
f
[
k
1
]
+
(
c
[
i
]
−
L
)
2
−
2
∗
c
[
k
1
]
∗
(
c
[
i
]
−
L
)
+
c
[
k
1
]
2
<
f
[
k
2
]
+
(
c
[
i
]
−
L
)
2
+
2
∗
(
c
[
i
]
−
L
)
∗
c
[
k
2
]
+
c
[
k
2
]
2
f[k1]+(c[i]-L)^2-2*c[k1]*(c[i]-L)+c[k1]^2<f[k2]+(c[i]-L)^2+2*(c[i]-L)*c[k2]+c[k2]^2
f[k1]+(c[i]−L)2−2∗c[k1]∗(c[i]−L)+c[k1]2<f[k2]+(c[i]−L)2+2∗(c[i]−L)∗c[k2]+c[k2]2
继续拆括号,移项,得
f
[
k
1
]
+
c
[
k
1
]
2
−
(
f
[
k
2
]
+
c
[
k
2
]
2
)
c
[
k
1
]
−
c
[
k
2
]
<
2
∗
(
c
[
i
]
−
L
)
\frac{f[k1]+c[k1]^2-(f[k2]+c[k2]^2)}{c[k1]-c[k2]}<2*(c[i]-L)
c[k1]−c[k2]f[k1]+c[k1]2−(f[k2]+c[k2]2)<2∗(c[i]−L)
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define LL long long
LL f[51000],list[51000],c[51000];
LL sqr(LL x) {return x*x;}
LL Y(LL i) {return f[i]+sqr(c[i]);}
LL X(LL i) {return c[i];}
int main()
{
int n,L;
scanf("%d%d",&n,&L);
for (int i=1;i<=n;i++)
{
int cc;
scanf("%d",&cc);
c[i]=c[i-1]+cc+1;
}
int head=1,tail=1;
list[0]=0; L++;
for (int i=1;i<=n;i++)
{
while (head+1<=tail && ( Y(list[head]) - Y(list[head+1]) ) >= 2*(c[i]-L) * (X(list[head])-X(list[head+1]))) head++;
f[i]=f[list[head]]+sqr(c[i]-c[list[head]]-L);
while (head<=tail-1 && ( Y(list[tail-1]) - Y(list[tail])) * (X(list[tail])-X(i)) >= ( Y(list[tail]) - Y(i)) * (X(list[tail-1]) - X(list[tail]))) tail--;
list[++tail]=i;
}
printf("%lld\n",f[n]);
return 0;
}
loj#10189. 「一本通 5.6 练习 2」仓库建设
https://loj.ac/problem/10189
注意题意,n是山脚。 其实题目就相当于任务安排2,分割成几段,每段只能选连续的数
当前在i建一个仓库,上一个仓库是在k这个位置建的
有dp方程
f
[
i
]
=
f
[
k
]
+
c
o
s
t
(
k
+
1
i
)
+
c
[
i
]
f[i]=f[k]+cost(k+1~i)+c[i]
f[i]=f[k]+cost(k+1 i)+c[i]
需要能够
O
(
1
)
O(1)
O(1)查询cost。
先预处理出货物走到工厂1的距离(p[i]*x[i]),新建一个厂就减去剩下的花费
如下图 红色为a的值为⑥走到1的花费,蓝色为(p[k+1]*x[k+1])
假设在⑥建厂,上一个厂建在②,那么③要运到⑥的花费即为两值之差
用a[i]记录
p
[
i
]
∗
x
[
i
]
p[i]*x[i]
p[i]∗x[i]的前缀和,pp[i]记录p[i]的前缀和
有dp方程
f
[
i
]
=
m
i
n
(
f
[
k
]
+
(
p
p
[
i
]
−
p
p
[
k
]
)
∗
x
[
i
]
−
(
a
[
i
]
−
a
[
k
]
)
+
c
[
i
]
)
f[i]=min(f[k]+(pp[i]-pp[k])*x[i]-(a[i]-a[k])+c[i])
f[i]=min(f[k]+(pp[i]−pp[k])∗x[i]−(a[i]−a[k])+c[i])
求斜率方程 ,设k1<k2
f
[
k
1
]
+
(
p
p
[
i
]
−
p
p
[
k
1
]
)
∗
x
[
i
]
−
(
a
[
i
]
−
a
[
k
1
]
)
+
c
[
i
]
>
f
[
k
2
]
+
(
p
p
[
i
]
−
p
p
[
k
2
]
)
∗
x
[
i
]
−
(
a
[
i
]
−
a
[
k
2
]
)
+
c
[
i
]
f[k1]+(pp[i]-pp[k1])*x[i]-(a[i]-a[k1])+c[i]>f[k2]+(pp[i]-pp[k2])*x[i]-(a[i]-a[k2])+c[i]
f[k1]+(pp[i]−pp[k1])∗x[i]−(a[i]−a[k1])+c[i]>f[k2]+(pp[i]−pp[k2])∗x[i]−(a[i]−a[k2])+c[i]
拆括号,移项,得
f
[
k
1
]
+
a
[
k
1
]
−
(
f
[
k
2
]
+
a
[
k
2
]
)
p
p
[
k
1
]
−
p
p
[
k
2
]
>
x
[
i
]
\frac{f[k1]+a[k1]-(f[k2]+a[k2])}{pp[k1]-pp[k2]}>x[i]
pp[k1]−pp[k2]f[k1]+a[k1]−(f[k2]+a[k2])>x[i]
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define LL long long
LL f[1100000],x[1100000],pp[1100000],a[1100000];
int list[1100000],c[1100000],p[1100000];
LL X(int i) {return pp[i];}
LL Y(int i) {return f[i]+a[i];}
int main()
{
int n;
scanf("%d",&n);
for (int i=1;i<=n;i++)
{
scanf("%d%d%d",&x[i],&p[i],&c[i]);
pp[i]=pp[i-1]+p[i];
a[i]=a[i-1]+x[i]*p[i];
}
int head=1,tail=1;
//list[1]=1; memset(f,0,sizeof(f)); f[1]=c[1];
for (int i=1;i<=n;i++)
{
while (head+1<=tail&& Y(list[head]) - Y(list[head+1]) >= x[i] * (X(list[head]) - X(list[head+1])) ) head++;
f[i]=f[list[head]]+(pp[i]-pp[list[head]])*x[i]-(a[i]-a[list[head]])+c[i];
while (head<=tail-1&& (Y(list[tail-1]) - Y(list[tail])) * (X(list[tail]) - X(i)) >= (Y(list[tail]) - Y(i)) * (X(list[tail-1]) - X(list[tail])) ) tail--;
list[++tail]=i;
}
printf("%lld\n",f[n]);
return 0;
}
loj#10190. 「一本通 5.6 练习 3」特别行动队
https://loj.ac/problem/10190
怎么感觉习题比例题还水…
公式也已经给出来了
s[i]为战斗力的前缀和
dp方程:
f
[
i
]
=
m
i
n
(
f
[
k
]
+
a
∗
(
s
[
i
]
−
s
[
k
]
)
2
+
b
∗
(
s
[
i
]
−
s
[
k
]
+
c
)
)
f[i]=min(f[k]+a*(s[i]-s[k])^2+b*(s[i]-s[k]+c))
f[i]=min(f[k]+a∗(s[i]−s[k])2+b∗(s[i]−s[k]+c))
斜率方程: f [ k 1 ] − f [ k 2 ] + a ∗ s [ k 1 ] 2 − a ∗ s [ k 2 ] 2 − b ∗ s [ k 1 ] + b ∗ s [ k 2 ] s [ k 1 ] − s [ k 2 ] < 2 ∗ a ∗ s [ i ] \frac{f[k1]-f[k2]+a*s[k1]^2-a*s[k2]^2-b*s[k1]+b*s[k2]}{s[k1]-s[k2]}<2*a*s[i] s[k1]−s[k2]f[k1]−f[k2]+a∗s[k1]2−a∗s[k2]2−b∗s[k1]+b∗s[k2]<2∗a∗s[i]
没开long long见祖宗啊
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define LL long long
LL f[1100000],s[1100000];
int list[1100000],n,a,b,c;
LL sqr(LL x) {return x*x;}
LL Y(int i) {return f[i]+a*sqr(s[i])-b*s[i];}
LL X(int i) {return s[i];}
int main()
{
scanf("%d%d%d%d",&n,&a,&b,&c);
for (int i=1;i<=n;i++)
{
int x;
scanf("%d",&x);
s[i]=s[i-1]+x;
}
int head=1,tail=1; //f[1]=a*sqr(s[1])+b*s[1]+c; list[2]=1;
for (int i=1;i<=n;i++)
{
while (head+1<=tail&& (Y(list[head]) - Y(list[head+1])) <(2*a*s[i])*(X(list[head]) -X(list[head+1]))) head++;
f[i]=f[list[head]]+a*sqr(s[i]-s[list[head]])+b*(s[i]-s[list[head]])+c;
while (head<=tail-1&& (Y(list[tail-1]) - Y(list[tail])) * (X(list[tail]) - X(i)) < (Y(list[tail]) - Y(i)) * (X(list[tail-1]) - X(list[tail]))) tail--;
list[++tail]=i;
}
printf("%lld\n",f[n]);
return 0;
}
loj#10191. 「一本通 5.6 练习 4」打印文章
https://loj.ac/problem/10191
也是给出了公式了
c[i]为花费的前缀和
dp方程:
f
[
i
]
=
m
i
n
(
f
[
k
]
+
(
s
[
i
]
−
s
[
k
]
)
2
+
M
)
f[i]=min(f[k]+(s[i]-s[k])^2+M)
f[i]=min(f[k]+(s[i]−s[k])2+M)
斜率方程:
f
[
k
1
]
−
f
[
k
2
]
+
s
[
k
1
]
2
−
s
[
k
2
]
2
s
[
k
1
]
−
s
[
k
2
]
>
2
∗
s
[
i
]
\frac{f[k1]-f[k2]+s[k1]^2-s[k2]^2}{s[k1]-s[k2]}>2*s[i]
s[k1]−s[k2]f[k1]−f[k2]+s[k1]2−s[k2]2>2∗s[i]
多组数据
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define LL long long
LL f[510000],s[510000];
int list[510000];
LL sqr(LL x) {return x*x;}
LL Y(int i) {return f[i]+sqr(s[i]);}
LL X(int i) {return s[i];}
int main()
{
int n,m;
while (scanf("%d%d",&n,&m)!=EOF)
{
s[0]=0;
for (int i=1;i<=n;i++)
{
scanf("%lld",&s[i]);
s[i]+=s[i-1];
}
int head=1,tail=1; list[1]=0; f[0]=0;
for (int i=1;i<=n;i++)
{
while (head+1<=tail&& (Y(list[head]) - Y(list[head+1])) >= 2*s[i]*(X(list[head])-X(list[head+1]))) head++;
f[i]=f[list[head]]+sqr(s[i]-s[list[head]])+m;
while (head<=tail-1&& (Y(list[tail-1]) - Y(list[tail])) * (X(list[tail])-X(i)) >= (Y(list[tail]) - Y(i)) * (X(list[tail-1]) - X(list[tail]))) tail--;
list[++tail]=i;
}
printf("%lld\n",f[n]);
}
return 0;
}
loj#10192. 「一本通 5.6 练习 5」锯木厂选址
https://loj.ac/problem/10192
a[i]和前面仓库建设的定义是一样的
设第一个仓库建在k,第二个仓库建在i ,且k<i ,s[i]为w[i]的前缀和
f
[
i
]
=
c
o
s
t
(
1
,
k
)
+
c
o
s
t
(
k
+
1
,
i
)
+
c
o
s
t
(
i
+
1
,
n
+
1
)
f[i]=cost(1,k)+cost(k+1,i)+cost(i+1,n+1)
f[i]=cost(1,k)+cost(k+1,i)+cost(i+1,n+1)
dp方程:
f
[
i
]
=
m
i
n
(
f
[
i
]
+
s
[
k
]
∗
d
[
k
]
−
a
[
k
]
+
(
s
[
i
]
−
s
[
k
]
)
∗
d
[
i
]
−
(
a
[
i
]
−
a
[
k
]
)
+
(
s
[
n
+
1
]
−
s
[
i
]
)
∗
d
[
n
]
−
(
a
[
n
]
−
a
[
i
]
)
)
f[i]=min(f[i]+s[k]*d[k]-a[k]+(s[i]-s[k])*d[i]-(a[i]-a[k])+(s[n+1]-s[i])*d[n]-(a[n]-a[i]))
f[i]=min(f[i]+s[k]∗d[k]−a[k]+(s[i]−s[k])∗d[i]−(a[i]−a[k])+(s[n+1]−s[i])∗d[n]−(a[n]−a[i]))
斜率方程:(注意s[k1]<s[k2])
f
[
i
]
=
s
[
k
1
]
∗
d
[
k
1
]
−
s
[
k
2
]
∗
d
[
k
2
]
s
[
k
1
]
−
s
[
k
2
]
<
d
[
i
]
f[i]=\frac{s[k1]*d[k1]-s[k2]*d[k2]}{s[k1]-s[k2]}<d[i]
f[i]=s[k1]−s[k2]s[k1]∗d[k1]−s[k2]∗d[k2]<d[i]
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define LL long long
LL f[210000],w[210000],d[210000],a[210000];
int list[210000];
LL Y(LL i) {return w[i]*d[i];}
LL X(LL i) {return w[i];}
int main()
{
int n;
scanf("%d",&n);
for (int i=1;i<=n;i++)
{
int x,y;
scanf("%d%d",&x,&y);
d[i+1]=d[i]+y;
a[i+1]=a[i]+x*d[i];
w[i]=w[i-1]+x;
}
int head=1,tail=1; //list[1]=1; f[1]=a[n]-a[1];
LL ans=9223372036854775807;
for (int i=1;i<=n;i++)
{
while (head+1<=tail&& (Y(list[head]) - Y(list[head+1])) >= d[i] * (X(list[head]) - X(list[head+1]))) head++;
f[i]=w[list[head]]*d[list[head]]-a[list[head]]+(w[i]-w[list[head]])*d[i]-(a[i]-a[list[head]])+(w[n]-w[i])*d[n+1]-(a[n+1]-a[i]);
while (head<=tail-1&& (Y(list[tail-1]) - Y(list[tail])) * (X(list[tail]) - X(i)) >= (Y(list[tail]) - Y(i)) * (X(list[tail-1]) - X(list[tail]))) tail--;
list[++tail]=i;
ans=min(ans,f[i]);
}
printf("%lld\n",ans);
return 0;
}
完结撒花!!
肝死我了这篇blog