关于「斜率优化 DP」戳 这里(加了密码,暂时不公开)。
下面都是一些斜率优化的入门题(按套路做,单调队列维护凸包),可以 从后往前看 自己推一推 QwQ。
1. 「HNOI 2008」玩具装箱
Problem:给定一个长度为 \(n\) 的序列 \(a_1,a_2,\cdots,a_n\) 以及常数 \(m\)。现要将 \(a\) 分成若干段,对于一段 \([l,r]\),它的代价为 \((r-l+\sum_{i=l}^r a_i-m)^2\)。求分段的最小代价。
\(1\leq n\leq 5\times 10^4,1\leq m,a_i\leq 10^7\)。
Solution:令 \(f_i\) 表示考虑了前 \(i\) 个数,分成若干段的最小代价。记 \(S_k=\sum_{i=1}^k a_i\)。那么有:
为了方便化简,我们令 \(G_i=S_i+i\),得:
任取 \(j,k\) 且满足 \(0≤k<j<i\),若从 \(j\) 转移比 \(k\) 更优,则有(接下来把平方拆开然后化简即可):
化简过程(省略部分步骤)
一般化简大概是将 只 与 \(j,k\) 有关的移到右侧,与 \(i\) 有关的移到左侧。
由于 \(a_i\) 为正整数,所以 \(G_j>G_k\),可以将 \(G_j-G_k\) 直接移到右侧,得到:
然后用单调队列维护一个下凸壳转移即可(维护一个斜率递增的凸壳)。
#include<bits/stdc++.h> #define int long long using namespace std; const int N=5e4+5; int n,m,x,s[N],g[N],f[N],q[N]; int get(int x){ return f[x]+g[x]*g[x]; } double slope(int i,int j){ return 1.0*(get(i)-get(j))/(g[i]-g[j]); } int sqr(int x){return x*x;} signed main(){ scanf("%lld%lld",&n,&m); for(int i=1;i<=n;i++) scanf("%lld",&x),s[i]=s[i-1]+x,g[i]=s[i]+i; int l=0,r=0; for(int i=1;i<=n;i++){ while(l<r&&slope(q[l],q[l+1])<=2*(g[i]-m-1)) l++; f[i]=f[q[l]]+sqr(g[i]-g[q[l]]-(m+1)); while(l<r&&slope(q[r-1],q[r])>=slope(q[r],i)) r--; q[++r]=i; } printf("%lld\n",f[n]); return 0; }
2. 「CEOI 2004」锯木厂选址
Problem:从山顶上到山底下沿着一条直线种植了 \(n\) 棵老树。第 \(i\) 棵树的重量为 \(w_i\),第 \(i\) 棵树和第 \(i+1\) 棵树之间的距离为 \(d_i\)。现在要将它们砍下来运送到锯木厂。
木材只能朝山下运。山脚下有一个锯木厂。另外两个锯木厂将新修建在山路上。你必须决定在哪里修建这两个锯木厂,使得运输的费用总和最小。假定运输每公斤木材每米需要一分钱。
求最小运输费用。\(2\leq n\leq 2\times 10^4,1\leq w_i\leq 10^4,0\leq d_i\leq 10^4\)。
Solution:
令 \(f_i\) 为以 \(i\) 作为第二个锯木厂的最小花费。记 \(S_i\) 为 \(w_i\) 的前缀和,\(tot\) 为 \(w_i\) 的总和(也就是将所有树全部运送到山脚下的费用),\(dis_i\) 为 \(d_i\) 的后缀和。即,\(S_i=\sum_{j=1}^i w_j,dis_i=\sum_{j=i}^n d_j\)。则有:
一些解释
相当于是枚举第一个锯木厂的位置 \(j\)。那么在 \(i,j\,(j<i)\) 处修了锯木厂的花费为:将 \(tot\) 减去 从 \(j\) 厂运送到山脚的额外花费 \(S_j\times dis_j\)(\(j\) 处修建了锯木厂,那么 \([1,j]\) 的树只需运送到 \(j\),不用运送到山脚下),再减去 从 \(i\) 厂运送到山脚的额外花费 \((S_i-S_j)\times dis_i\)(\(j<i\),\(i\) 处修了锯木厂,那么 \((j,i]\) 的树只需运送到 \(i\))。
然后这显然是可以斜率优化的,按套路来就可以了。
当 \(k<j<i\) 时,若 \(j\) 比 \(k\) 更优,则有:
化简过程
\(w_i\) 为正整数,所以 \(S_j>S_k\),可以将 \(S_j-S_k\) 直接移到左侧:
由于 \(dis_i\) 是递减的,我们可以维护一个上凸壳转移(维护一个斜率递减的凸壳)。
#include<bits/stdc++.h> #define int long long using namespace std; const int N=2e4+5; int n,w[N],d[N],dis[N],s[N],tot,q[N]; int get(int x){ return s[x]*dis[x]; } double slope(int i,int j){ return 1.0*(get(i)-get(j))/(s[i]-s[j]); } signed main(){ scanf("%lld",&n); for(int i=1;i<=n;i++) scanf("%lld%lld",&w[i],&d[i]); for(int i=n;i>=1;i--) dis[i]=dis[i+1]+d[i]; for(int i=1;i<=n;i++) s[i]=s[i-1]+w[i],tot+=w[i]*dis[i]; int l=0,r=0,ans=1e18; for(int i=1;i<=n;i++){ while(l<r&&slope(q[l],q[l+1])>=dis[i]) l++; ans=min(ans,tot-s[q[l]]*dis[q[l]]-(s[i]-s[q[l]])*dis[i]); while(l<r&&slope(q[r-1],q[r])<=slope(q[r],i)) r--; q[++r]=i; } printf("%lld\n",ans); return 0; }
3. 「ZJOI 2007」仓库建设
Problem:略。
Solution:与上一题类似。令 \(f_i\) 表示在 \(i\) 位置建设了仓库的最小代价。记 \(S_i=\sum_{j=1}^i p_j,G_i=\sum_{j=1}^i x_jp_j\)。
一些解释
枚举上一个仓库的位置。那么只需将 \((j,i]\) 的产品运送到 \(i\),无需再运到山脚。
然后前缀和优化。
然后根据套路做。当 \(k<j<i\) 时,若 \(j\) 比 \(k\) 更优,则有:
化简过程
显然有 \(S_j>S_k\),可将 \(S_j-S_k\) 移到右侧:
#define int long long using namespace std; const int N=1e6+5; int n,x[N],p[N],c[N],s[N],g[N],f[N],q[N]; int get(int x){ return f[x]+g[x]; } double slope(int i,int j){ return 1.0*(get(i)-get(j))/(s[i]-s[j]); } signed main(){ scanf("%lld",&n); for(int i=1;i<=n;i++){ scanf("%lld%lld%lld",&x[i],&p[i],&c[i]); s[i]=s[i-1]+p[i],g[i]=g[i-1]+x[i]*p[i]; } int l=0,r=0; for(int i=1;i<=n;i++){ while(l<r&&slope(q[l],q[l+1])<=x[i]) l++; f[i]=f[q[l]]+x[i]*(s[i]-s[q[l]])-(g[i]-g[q[l]])+c[i]; while(l<r&&slope(q[r-1],q[r])>=slope(q[r],i)) r--; q[++r]=i; } printf("%lld\n",f[n]); return 0; }
4. 「BZOJ 1597」土地购买
Problem:有 \(n\) 块土地,第 \(i\) 块土地长为 \(x_i\)、宽为 \(y_i\)。现要将这些土地划分为若干组(每块土地都应该属于且仅属于其中一组。也可以一块土地单独一组),一组土地的代价为这些土地中最大的长乘以最大的宽,即 \(\max\{x_i\}\times \max\{y_i\}\)。求划分的最小代价之和。
\(1\leq n\leq 5\times 10^4,1\leq x_i,y_i\leq 10^6\)。
Solution:首先,对于土地 \(i,j\),若 \(x_i\geq x_j\) 且 \(y_i\geq y_j\),则土地 \(j\) 显然是无用的(可以将 \(j\) 和 \(i\) 分为一组,\(j\) 没有贡献)。
考虑将所有土地按 \(x_i\) 降序为第一优先级,\(y_i\) 升序为第二优先级。此时对于连续的一段土地 \([l,r]\),\(\max\{x_i\}\) 一定为 \(x_l\),但 \(\max\{y_i\}\) 不确定。考虑通过剔除无用元素使得第二位也存在单调性,使得 \(\max\{y_i\}\) 一定为 \(y_r\)。具体见代码。
此时对于划分出的一段 \([l,r]\),其代价为 \(x_l\cdot y_r\)。令 \(f_i\) 表示按此顺序划分前 \(i\) 块土地的最小代价。转移时,枚举上一组土地的结尾。
当 \(k<j<i\) 时,若 \(j\) 比 \(k\) 更优,则有:
化简过程
由于 \(x_i\) 是降序排序的,所以 \(x_{j+1}\leq x_{k+1}\),\(x_{j+1}-x_{k+1}\leq 0\)。将 \(x_{j+1}-x_{k+1}\) 移到左侧时要变号:
维护下凸壳转移即可。
#include<bits/stdc++.h> #define int long long using namespace std; const int N=5e4+5; int n,m,f[N],q[N],lst; bool vis[N]; struct data{ int x,y; }a[N]; bool cmp(data x,data y){ return x.x!=y.x?x.x>y.x:x.y<y.y; } double slope(int i,int j){ return 1.0*(f[j]-f[i])/(a[i+1].x-a[j+1].x); } signed main(){ scanf("%lld",&n); for(int i=1;i<=n;i++) scanf("%lld%lld",&a[i].x,&a[i].y); sort(a+1,a+1+n,cmp); for(int i=1;i<=n;i++){ //去除无贡献元素 if(i!=1&&a[i].x<=a[lst].x&&a[i].y<=a[lst].y) vis[i]=1; else lst=i; } for(int i=1;i<=n;i++) if(!vis[i]) a[++m]=a[i]; int l=0,r=0; for(int i=1;i<=m;i++){ while(l<r&&slope(q[l],q[l+1])<=a[i].y) l++; f[i]=f[q[l]]+a[q[l]+1].x*a[i].y; while(l<r&&slope(q[r-1],q[r])>=slope(q[r],i)) r--; q[++r]=i; } printf("%lld\n",f[m]); return 0; }
5. 「APIO 2010」特别行动队
Problem:有一支 \(n\) 名士兵的部队,士兵从 \(1\) 到 \(n\) 编号,编号为 \(i\) 的士兵的初始战斗力为 \(x_i\)。现要将他们拆分成若干组,同一组中队员的编号应该连续,即为形如 \((i,i+1,\cdots,i+k)\) 的序列。
对于一个组,它的初始战斗力 \(X\) 为组内士兵初始战斗力之和,即 \(X=x_i+x_{i+1}+\cdots+x_{i+k}\)。它的修正战斗力为 \(X'=aX^2+bX+cX\),其中 \(a,b,c\) 是已知的系数(\(a<0\))。
求每组修正战斗力之和的最大值。
\(1\leq n\leq 10^6,-5\leq a\leq -1,-10^7\leq b,c\leq 10^7,1\leq x_i\leq 100\)。
Solution:令 \(f_i\) 表示前 \(i\) 个人的战斗力之和的最大值。记 \(S_i=\sum_{j=1}^i x_j\)。
当 \(k<j<i\) 时,若 \(j\) 比 \(k\) 更优,则有:
化简过程
因为 \(x_i\) 为正整数,所以 \(S_j>S_k\),将 \(S_j-S_k\) 移到右侧得:
不等式左侧是单调递减的(题目中保证 \(a<0\),且显然 \(S_i\) 递增),右侧分母上的前缀和是单调递增的。
维护上凸壳转移即可(斜率递减),每次的最优决策就是队首。
#include<bits/stdc++.h> #define int long long using namespace std; const int N=1e6+5; int n,a,b,c,x[N],s[N],q[N],f[N]; int sqr(int x){return x*x;} int get(int x){ return f[x]+a*sqr(s[x])-b*s[x]; } double slope(int i,int j){ return 1.0*(get(i)-get(j))/(s[i]-s[j]); } signed main(){ scanf("%lld%lld%lld%lld",&n,&a,&b,&c); for(int i=1;i<=n;i++) scanf("%lld",&x[i]),s[i]=s[i-1]+x[i]; int l=0,r=0; for(int i=1;i<=n;i++){ while(l<r&&slope(q[l],q[l+1])>=2*a*s[i]) l++; f[i]=f[q[l]]+a*sqr(s[i]-s[q[l]])+b*(s[i]-s[q[l]])+c; while(l<r&&slope(q[r-1],q[r])<=slope(q[r],i)) r--; q[++r]=i; } printf("%lld\n",f[n]); return 0; }
6. 「APIO 2014」序列分割
Problem:给出一个长度为 \(n\) 的非负整数序列 \(\{a_n\}\)。先要将序列分为 \(k+1\) 个非空的块。为了得到 \(k+1\) 块,你需要重复下面的操作 \(k\) 次:
- 选择一个有超过一个元素的块(初始时你只有一块,即整个序列);
- 选择两个相邻元素把这个块从中间分开,得到两个非空的块。
每次操作后将获得那两个新产生的块的元素和的乘积的分数。最大化最后的总得分,要求输出方案。
\(2\leq n\leq 10^5,1\leq k\leq \min(n-1,200),0\leq a_i\leq 10^4\)。
Solution:
我们首先证明答案和分割顺序无关。
如果我们有长度为 \(3\) 的序列 \(x,y,z\) 将其分为 \(3\) 部分,有如下两种分割方法:
- 先在 \(x\) 后面分割,答案为 \(x(y+z)+yz\) 即为 \(xy+yz+zx\)。
- 先在 \(y\) 后面分割,答案为 \((x+y)z+xy\) 即为 \(xy+yz+zx\)。
然后这个结论可以扩展到任意长度的序列(分析一下贡献),证毕。
令 \(F_{i,j}\) 表示前 \(i\) 个数进行 \(j\) 次分割的最大得分。记 \(S_i\) 为 \(a_i\) 的前缀和。
为了方便表述,记 \(F_{i,k}\) 为 \(f_i\),\(F_{j,k-1}\) 为 \(g_j\),相当于把 \(F\) 的第二维滚动掉了。
当 \(k<j<i\) 时,若 \(j\) 比 \(k\) 更优,则有:
化简过程
显然 \(S_j\geq S_k\),所以 \(S_k-S_j\leq 0\),将 \(S_k-S_j\) 移到右边需变号:
维护下凸壳转移即可。然后输出方案的话记一个 \(pre\) 表示从哪里转移过来。
#include<bits/stdc++.h> #define int long long using namespace std; const int N=1e5+5,M=210; int n,k,a[N],s[N],g[N],f[N],pre[N][M],q[N],x; int get(int x){ return g[x]-s[x]*s[x]; } double slope(int i,int j){ if(s[i]==s[j]) return -1e18; //注意此题中,a[i] 为非负整数,可能会取到 0,所以 s[k]-s[j] 的值可能为 0。这种情况需特判,slope 需返回 -inf(否则算斜率的时候除以 0 就挂了)。 return 1.0*(get(i)-get(j))/(s[j]-s[i]); } signed main(){ scanf("%lld%lld",&n,&k); for(int i=1;i<=n;i++) scanf("%lld",&a[i]),s[i]=s[i-1]+a[i]; for(int j=1;j<=k;j++){ int l=0,r=0; for(int i=1;i<=n;i++){ while(l<r&&slope(q[l],q[l+1])<=s[i]) l++; f[i]=g[q[l]]+s[q[l]]*(s[i]-s[q[l]]),pre[i][j]=q[l]; while(l<r&&slope(q[r-1],q[r])>=slope(q[r],i)) r--; q[++r]=i; } memcpy(g,f,sizeof(f)); } printf("%lld\n",f[n]),x=n; for(int i=k;i>=1;i--) x=pre[x][i],printf("%lld%c",x,i==1?'\n':' '); return 0; }
7. 「SDOI 2016」征途
Problem:给出一个有 \(n\) 个数的序列 \(\{a_n\}\),现要把其分为 \(m\) 段,设每段的权值为该段 \(a_i\) 之和,最小化这 \(m\) 段的方差 \(v\),输出 \(v\times m^2\)。
\(1\leq n\leq 3000,\sum a_i\leq 30000\)。
Solution:与上一题类似。
设 \(b_i\) 为每段的权值,\(\overline b\) 为平均数,我们要最小化:
将平方拆开,得到:
继续化简,并代入 \(\overline b=\frac{\sum_{i=1}^mb_i}{m}\),得到:
化简过程
发现后面那部分的 \(\left(\sum_{i=1}^mb_i\right)^2\) 等于 \(\left(\sum_{i=1}^n a_i\right)^2\) 为定值,我们现在要最小化 \(\sum_{i=1}^m b_i^2\)。
令 \(F_{i,j}\) 表示前 \(i\) 个数分为 \(j\) 段的最小值。记 \(S_i\) 为 \(a_i\) 的前缀和。
将 \(F\) 的第二维用滚动数组滚掉。记 \(F_{i,k}\) 为 \(f_i\),\(F_{j,k-1}\) 为 \(g_j\)。
当 \(k<j<i\) 时,若 \(j\) 比 \(k\) 更优,则有:
维护下凸壳转移即可。
#include<bits/stdc++.h> #define int long long using namespace std; const int N=3e4+5; int n,m,x,s[N],f[N],g[N],q[N]; int sqr(int x){return x*x;} int get(int x){ return g[x]+sqr(s[x]); } double slope(int i,int j){ return 1.0*(get(i)-get(j))/(s[i]-s[j]); } signed main(){ scanf("%lld%lld",&n,&m); for(int i=1;i<=n;i++) scanf("%lld",&x),s[i]=s[i-1]+x,g[i]=s[i]*s[i]; for(int j=2;j<=m;j++){ int l=0,r=0; for(int i=1;i<=n;i++){ while(l<r&&slope(q[l],q[l+1])<=2*s[i]) l++; f[i]=g[q[l]]+sqr(s[i]-s[q[l]]); while(l<r&&slope(q[r-1],q[r])>=slope(q[r],i)) r--; q[++r]=i; } memcpy(g,f,sizeof(f)); } printf("%lld\n",m*f[n]-sqr(s[n])); return 0; }
8. 「Codeforces 311B」Cats Transport
「Codeforces 311B」Cats Transport
Problem:小 S 是农场主,他养了 \(m\) 只猫,雇了 \(p\) 位饲养员。农场中有一条笔直的路,路边有 \(n\) 座山,从 \(1\) 到 \(n\) 编号。第 \(i\) 座山与第 \(i?1\) 座山之间的距离是 \(d_i\)。饲养员都住在 \(1\) 号山上。
有一天,猫出去玩。第 \(i\) 只猫去 \(h_i\) 号山玩,玩到时刻 \(t_i\) 停止,然后在原地等饲养员来接。饲养员们必须回收所有的猫。每个饲养员沿着路从 \(1\) 号山走到 \(n\) 号山,把各座山上已经在等待的猫全部接走。饲养员在路上行走需要时间,速度为 \(1\) 米每单位时间。饲养员在每座山上接猫的时间可以忽略,可以携带的猫的数量为无穷大。
你的任务是规划每个饲养员从 \(1\) 号山出发的时间,使得所有猫等待时间的总和尽量小。饲养员出发的时间可以为负。
\(2\leq n\leq 10^5,1\leq m\leq10^5,1\leq p\leq 100\)。
Solution:设 \(a_i=t_i-\sum_{j=2}^{h_i}d_j\),也就是让第 \(i\) 只猫不等待的出发时间。如果有人从 \(T\) 时刻出发,那么等待时间为 \(T-a_i\)。
考虑将 \(a_i\) 从小到大排序,那么每一个饲养员应该会带走一段连续区间的猫。
令 \(F_{i,k}\) 表示 \(k\) 个饲养员带走前 \(i\) 只小猫的最少等待时间。记 \(S_i\) 为 \(a_i\) 的前缀和。
即,第 \(k\) 个饲养员带走 \((j,i]\) 的小猫,那么就在 \(a_i\) 出发,等待时间为 \(a_i(i-1)-(S_i-S_j)\)。
用滚动数组将 \(F\) 的第二维滚掉。设 \(f_i=F_{i,k},g_j=F_{j,k-1}\)。则:
当 \(k<j<i\) 时,若 \(j\) 比 \(k\) 更优,则有:
化简过程
维护下凸壳转移即可。
#include<bits/stdc++.h> #define int long long using namespace std; const int N=1e5+5; int n,m,p,d[N],a[N],h[N],t[N],s[N],f[N],g[N],q[N]; int get(int x){ return g[x]+s[x]; } double slope(int i,int j){ return 1.0*(get(i)-get(j))/(i-j); } signed main(){ scanf("%lld%lld%lld",&n,&m,&p); for(int i=2;i<=n;i++) scanf("%lld",&d[i]),d[i]+=d[i-1]; for(int i=1;i<=m;i++) scanf("%lld%lld",&h[i],&t[i]),a[i]=t[i]-d[h[i]]; sort(a+1,a+1+m); for(int i=1;i<=m;i++) s[i]=s[i-1]+a[i]; memset(g,0x3f,sizeof(f)),g[0]=0; for(int j=1;j<=p;j++){ int l=0,r=0; for(int i=1;i<=m;i++){ while(l<r&&slope(q[l],q[l+1])<=a[i]) l++; f[i]=g[q[l]]+a[i]*(i-q[l])-(s[i]-s[q[l]]); while(l<r&&slope(q[r-1],q[r])>=slope(q[r],i)) r--; q[++r]=i; } memcpy(g,f,sizeof(f)); } printf("%lld\n",f[m]); return 0; }
9. 「SDOI 2012」任务安排
Problem:\(n\) 个任务,标号为 \(1\) 到 \(n\),第 \(i\) 个任务单独完成所需的时间是 \(t_i\)。要求将 \(n\) 个任务分为若干批,每批包含相邻的若干任务。在每批任务开始前,机器需要启动时间 \(s\),完成这批任务所需的时间是各个任务需要时间的总和。
同一批任务将在同一时刻完成。每个任务的费用是它的完成时刻乘以一个费用系数 \(c_i\)。
求最小总费用。\(1\leq n\leq 3\times 10^5,1\leq s\leq 2^8,|t_i|\leq 2^8,0\leq c_i\leq 2^8\)。
Solution:令 \(f_{i,j}\) 表示前 \(i\) 个任务被分为 \(j\) 批的最小费用。记 \(T_i\) 表示 \(t_i\) 的前缀和,\(C_i\) 表示 \(c_i\) 的前缀和。
意思就是,第 \(j\) 批任务(包含 \((k,i]\) 的任务)的完成时间为 \(T_i+s\times j\),这批任务的费用和为 \((T_i+s\times j)\times \sum_{p=k+1}^i c_p\)。
注意到转移已经是 \(\mathcal O(1)\) 的了,考虑优化 DP 的状态。
发现 \(j\) 的作用仅是为了计算 \(j\) 批任务的启动时间和 \(s\times j\)。将当前这批任务(前 \(i\) 个任务分完了)分出后,会增加 \(s\) 等待的启动时间,则后续费用和会增加 \(\sum_{p=i+1}^n c_p\times s\)。考虑提前加进去,从而优化掉状态的第二维。这就是“费用提前计算”的思想。
当 \(k<j<i\) 时,若 \(j\) 比 \(k\) 更优,则有:
直接单调队列维护下凸壳:
int l=0,r=0; for(int i=1;i<=n;i++){ while(l<r&&slope(q[l],q[l+1])<=t[i]) l++; f[i]=f[q[l]]+t[i]*(c[i]-c[q[l]])+s*(c[n]-c[q[l]]); while(l<r&&slope(q[r-1],q[r])>=slope(q[r],i)) r--; q[++r]=i; }
然而这样是错的。注意到 \(t_i\) 可能为负,会导致 \(t_i\) 的前缀和 \(T_i\) 不一定单调,这影响了最优决策点的选择,无法使用单调队列取队首 选择最优决策点。
因此不能弹出队首,而是维护整个凸壳,每次查询最优决策点时在凸壳上二分,找到第一个使得左侧斜率小于 \(T_i\),右侧斜率不小于 \(T_i\) 的位置即为最优决策点。
#include<bits/stdc++.h> #define int long long using namespace std; const int N=3e5+5; int n,s,x,y,t[N],c[N],f[N],q[N]; int get(int x){ return f[x]-s*c[x]; } double slope(int i,int j){ if(c[i]==c[j]) return 1e18; return 1.0*(get(i)-get(j))/(c[i]-c[j]); } int find(int l,int r,int v){ int ans=r; while(l<=r){ int mid=(l+r)/2; if(slope(q[mid],q[mid+1])>=v) ans=mid,r=mid-1; else l=mid+1; } return q[ans]; } signed main(){ scanf("%lld%lld",&n,&s); for(int i=1;i<=n;i++){ scanf("%lld%lld",&x,&y); t[i]=t[i-1]+x,c[i]=c[i-1]+y; } int l=0,r=0; for(int i=1;i<=n;i++){ int pos=find(l,r,t[i]); f[i]=f[pos]+t[i]*(c[i]-c[pos])+s*(c[n]-c[pos]); while(l<r&&slope(q[r-1],q[r])>=slope(q[r],i)) r--; q[++r]=i; } printf("%lld\n",f[n]); return 0; }