「一本通」斜率优化dp学习笔记

总结:

如果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

https://loj.ac/problem/10184

这题应该是先教你搞出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 1N5000,可以放心 N 2 N^2 N2 dp

#include 
#include 
#include 
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

loj#10185. 「一本通 5.6 例 2」任务安排 2

https://loj.ac/problem/10185

(事实证明用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]Sc[j1]f[j2]+Sc[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]Sc[j1])(f[j2]Sc[j2])<t[i]
得到公式啦!
根据“及时排出无用决策”,本题应是要维护一个下凸壳

#include 
#include 
#include 
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])

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 
#include 
#include 
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])=(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[i1][k](s[j]s[k])+a[j](jk))

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[i1][k1](s[j]s[k1])+a[j](jk1)>f[i1][k2](s[j]s[k2])+a[j](jk2)
拆括号,抵消,移项,注意 k 1 − k 2 < 0 k1-k2<0 k1k2<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] k1k2(f[i1][k1]s[k1])(a[i1][k2]s[k2])<a[j]

上一题血的教训告诉我写代码还是把它弄成乘法吧

#include 
#include 
#include 
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=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]=1j<imin(f[j]+(sum[i]sum[j]+ij1L)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)22c[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 
#include 
#include 
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])
假设在⑥建厂,上一个厂建在②,那么③要运到⑥的花费即为两值之差
「一本通」斜率优化dp学习笔记_第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 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 
#include 
#include 
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]+as[k1]2as[k2]2bs[k1]+bs[k2]<2as[i]

没开long long见祖宗啊

#include 
#include 
#include 
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]2s[k2]2>2s[i]
多组数据

#include 
#include 
#include 
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 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] 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 
#include 
#include 
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

你可能感兴趣的:(loj,一本通提高篇,DP,斜率优化)