dp斜率优化
dp斜率优化
T1 hdu3507 打印文章
题目描述:
给定一个含 个数的数列 和 ,将 分为若干段 ,求所有子段的 之和的最小值.
思路:
将只含不确定性 的项放在左边,含有 的项放在右边:
每个决策 都对应着一个点 ,目标是使纵截距 最小.
维护一个下凸包:
如图所示b取最小:
**<!!!>此时满足: 线的斜率 点的决策斜率 线的斜率 **
利用单调队列维护决策点.
对于任意 ,必有 ,故不存在 点所示情况,即新的决策点必出现在下突壳的最右端.
维护下突壳:检查新加入的点 和之前两点 是否满足下凸性,若不满足则弹出队尾 ,然后将新决策点 入队.
随着 的增加,斜率 ,故一次查询的最优决策点之前的斜率 的线均无效,出队.
队头即是最优决策点.
(若 不满足单调递增(如 ),则不能直接弹出斜率 的所有决策点,队头也不一定是最优决策点,要在单调队列中二分出一个点 使得 )
Code:
#include<bits/stdc++.h>
#define ll long long
#define fr(i,r) for(int i=1;i<=r;++i)
#define For(i,l,r) for(int i=l;i<=r;++i)
#define Rof(i,r,l) for(int i=r;i>=l;--i)
#define pb push_back
#define pii pair<int,int>
#define mp(x,y) make_pair(x,y)
#define msec 800
using namespace std;
const int N=5e5+10,Inf=0x7fffffff;
char cch;
int res,zf;
inline int rd()
{
while((cch=getchar())<45);
if(cch^45)res=cch^48,zf=1;
else res=0,zf=-1;
while((cch=getchar())>=48)res=(res*10)+(cch^48);
return res*zf;
}
int n,m;
int s[N],dp[N];
int q[N],h,t;
inline int X(int k)
{
return s[k];
}
inline int Y(int k)
{
return dp[k]+s[k]*s[k];
}
inline int K_nodei(int k)
{
return 2*s[k];
}
inline int deltaX(int k1,int k2)
{
return X(k2)-X(k1);
}
inline int deltaY(int k1,int k2)
{
return Y(k2)-Y(k1);
}
inline bool Kjud1(int h,int i)
{
return bool(deltaY(q[h],q[h+1])<=K_nodei(i)*deltaX(q[h],q[h+1])); //A--a--B--b--C ,b的斜率大等于a => ka<=kb[已知] Δy(AB) / Δx(AB)<=kb => Δy(AB)<=kb*Δx(AB)
}
inline bool Kjud2(int h,int i)
{
return bool(deltaY(q[t],i)*deltaX(q[t-1],q[t])<=deltaY(q[t-1],q[t])*deltaX(q[t],i)); //k2<=k1 => y2/x2<=y1/x1 => y2*x1<=y1*x2
}
inline int fi(int i,int j)
{
return dp[j]+(s[i]-s[j])*(s[i]-s[j])+m;//回到最初的定义
}
inline void init()
{
h=t=0;
}
int main()
{
while(~scanf("%d%d",&n,&m))
{
init();
fr(i,n) s[i]=s[i-1]+rd();
fr(i,n)
{
while(h<t/*防队列退空,至少应当留有一项*/&&Kjud1(h,i)) ++h;//若前一个点到这个点的斜率<=该点的斜率要求(2*s[i]),加之斜率k单调递增,则前一个点已经无效
dp[i]=fi(i,q[h]/*当前最优决策点*/);
while(h<t&&Kjud2(h,i)) --t;
q[++t]=i;
}
cout<<dp[n];
}
return 0;
}
T2&T3 #P10184.任务安排 1 P2365 任务安排 & #P10185.任务安排 2
题目描述:
给定一个含 个数的数列 、 和 ,将 分为若干段 ,求所有子段的 之和的最小值.
思路:
<!!! 费用提前计算>↑
既然一次操作会使后面所有段的 均增加 且 只与 有关,那就一次性将后面所有的 全部进行一次更新,提前计算所需费用.
移项得:
每个决策 都对应着一个点 ,目标是使纵截距 最小,故维护一个下凸包.
同 , 依然满足单调递增,故可以用单调队列维护斜率 .
同 , 值也依然满足单调递增.
Code:
#include<bits/stdc++.h>
#define ll long long
#define int ll
#define fr(i,r) for(int i=1;i<=r;++i)
#define For(i,l,r) for(int i=l;i<=r;++i)
#define Rof(i,r,l) for(int i=r;i>=l;--i)
#define pb push_back
#define pii pair<int,int>
#define mp(x,y) make_pair(x,y)
#define msec 800
using namespace std;
const int N=3e5+10,Inf=0x7fffffff;
char cch;
int res,zf;
inline int rd()
{
while((cch=getchar())<45);
if(cch^45)res=cch^48,zf=1;
else res=0,zf=-1;
while((cch=getchar())>=48)res=(res*10)+(cch^48);
return res*zf;
}
int n=rd(),S=rd();
int sumC[N],sumT[N];
int q[N],h,t;
int dp[N];
inline int X(int k)
{
return sumC[k];
}
inline int Y(int k)
{
return dp[k]-S*sumC[k];
}
inline int K(int k)
{
return sumT[k];
}
inline int deltaX(int k1,int k2)
{
return X(k2)-X(k1);
}
inline int deltaY(int k1,int k2)
{
return Y(k2)-Y(k1);
}
inline bool Kjud1(int i)
{
return bool(deltaY(q[h],q[h+1])<=K(i)*deltaX(q[h],q[h+1]));
}
inline bool Kjud2(int i)
{
return bool(deltaY(q[t],i)*deltaX(q[t-1],q[t])<=deltaY(q[t-1],q[t])*deltaX(q[t],i));
}
inline int Fi(int i,int j)
{
return dp[j]+sumT[i]*(sumC[i]-sumC[j])+S*(sumC[n]-sumC[j]);
}
signed main()
{
fr(i,n) sumT[i]=sumT[i-1]+rd(),sumC[i]=sumC[i-1]+rd();
fr(i,n)
{
while(h<t&&Kjud1(i)) ++h;
dp[i]=Fi(i,q[h]);
while(h<t&&Kjud2(i)) --t;
q[++t]=i;
}
cout<<dp[n];
return 0;
}
T4 #P10186. 任务安排 3 P5785 [SDOI2012]任务安排
题目描述:
T2&T3
思路:
因为 可能 , 不再满足单调递增,所以不能再用单调队列来维护决策点.(注意:点的 和线的 不是一个东西,因为下突壳的维护,决策点之间依然满足 的单调递增,不过在对每个点进行查询时 不单调递增,故无法顺行一次查完)
二分查找最优决策点 ,使得 为满足 的最早的决策点(此时满足 ).
其他过程同
Code:
#include<bits/stdc++.h>
#define ll long long
#define int ll
#define fr(i,r) for(int i=1;i<=r;++i)
#define For(i,l,r) for(int i=l;i<=r;++i)
#define Rof(i,r,l) for(int i=r;i>=l;--i)
#define pb push_back
#define pii pair<int,int>
#define mp(x,y) make_pair(x,y)
#define msec 800
using namespace std;
const int N=3e5+10,Inf=0x7fffffff;
char cch;
int res,zf;
inline int rd()
{
while((cch=getchar())<45);
if(cch^45)res=cch^48,zf=1;
else res=0,zf=-1;
while((cch=getchar())>=48)res=(res*10)+(cch^48);
return res*zf;
}
int n=rd(),S=rd();
int sumC[N],sumT[N];
int q[N],h,t;
int dp[N];
inline int X(int k)
{
return sumC[k];
}
inline int Y(int k)
{
return dp[k]-S*sumC[k];
}
inline int K(int k)
{
return sumT[k];
}
inline int deltaX(int k1,int k2)
{
return X(k2)-X(k1);
}
inline int deltaY(int k1,int k2)
{
return Y(k2)-Y(k1);
}
inline bool Kjud1(int h,int i)
{
return bool(deltaY(q[h],q[h+1])<=K(i)*deltaX(q[h],q[h+1]));//h-->h+1斜率小于点斜率
}
inline bool Kjud2(int t,int i)
{
return bool(deltaY(q[t],i)*deltaX(q[t-1],q[t])<=deltaY(q[t-1],q[t])*deltaX(q[t],i));
}
inline int Fi(int i,int j)
{
return dp[j]+sumT[i]*(sumC[i]-sumC[j])+S*(sumC[n]-sumC[j]);
}
inline int bin_search(int i)
{
int l=h,r=t;//<-1 x>
while(l<r)
{
int mid=(l+r)>>1;//检查线段mid-->mid+1斜率
if(Kjud1(mid,i)) l=mid+1;
else r=mid;//<-1 x>
}
return q[l];
}
int p;
signed main()
{
fr(i,n) sumT[i]=sumT[i-1]+rd(),sumC[i]=sumC[i-1]+rd();
fr(i,n)
{
p=bin_search(i);
dp[i]=Fi(i,p);
while(h<t&&Kjud2(t,i)) --t;
q[++t]=i;
}
cout<<dp[n];
return 0;
}
T5 #P10188. 玩具装箱 P3195 玩具装箱
题目描述:
给定一个数列 和一个常数 ,将其分为若干 段,对于其中一段 ,
求 的最小值.
思路:
* 提到的移项方法是一种通法,但一个式子可能不止一种移项方法,不同移法的 可能会有所不同,但结果相同:
(还有更多方法)
本题需要维护 值最小,故维护一个下凸包.
其余同 .
Code:
#include<bits/stdc++.h>
#define ll long long
#define int ll
#define fr(i,r) for(int i=1;i<=r;++i)
#define For(i,l,r) for(int i=l;i<=r;++i)
#define Rof(i,r,l) for(int i=r;i>=l;--i)
#define pb push_back
#define pii pair<int,int>
#define mp(x,y) make_pair(x,y)
#define msec 800
using namespace std;
const int N=5e4+10,Inf=0x7fffffff;
char cch;
int res,zf;
inline int rd()
{
while((cch=getchar())<45);
if(cch^45)res=cch^48,zf=1;
else res=0,zf=-1;
while((cch=getchar())>=48)res=(res*10)+(cch^48);
return res*zf;
}
int n=rd(),L=rd();
int S[N];
int q[N],h,t;
int dp[N];
inline int X(int j)
{
return S[j];
}
inline int Y(int j)
{
return dp[j]+S[j]*S[j]+2*(L+1)*S[j];
}
inline int K(int i)
{
return 2*S[i];
}
inline int deltaX(int k1,int k2)
{
return X(k2)-X(k1);
}
inline int deltaY(int k1,int k2)
{
return Y(k2)-Y(k1);
}
inline bool Kjud1(int i)
{
return bool(deltaY(q[h],q[h+1])<=K(i)*deltaX(q[h],q[h+1]));
}
inline bool Kjud2(int i)
{
return bool(deltaY(q[t],i)*deltaX(q[t-1],q[t])<=deltaY(q[t-1],q[t])*deltaX(q[t],i));
}
inline int Fi(int i,int j)
{
return dp[j]+(S[i]-S[j]-(L+1))*(S[i]-S[j]-(L+1));
}
signed main()
{
fr(i,n) S[i]=S[i-1]+rd()+1;
fr(i,n)
{
while(h<t&&Kjud1(i)) ++h;
dp[i]=Fi(i,q[h]);
while(h<t&&Kjud2(i)) --t;
q[++t]=i;
}
cout<<dp[n];
return 0;
}
T6 P3648 [APIO2014]序列分割
题目描述:
你正在玩一个关于长度为 的非负整数序列的游戏。这个游戏中你需要把序列分成 个非空的块。为了得到 块,你需要重复下面的操作 次:
选择一个有超过一个元素的块(初始时你只有一块,即整个序列)
选择两个相邻元素把这个块从中间分开,得到两个非空的块。
每次操作后你将获得那两个新产生的块的元素和的乘积的分数。你想要最大化最后的总得分。
思路:
1.收益与切割顺序无关:
如上图,对于 这一序列中的三个相邻子段而言,
先切下 :
先切下 :
所有的子段均遵循此规律
由是可知得分与切割顺序无关.
于是乎就可以把题目改写为:
给定一个数列 ,将其分为 段,求最大的
每一个新加入的段的收益便为该段元素和*前面的所有元素的和.
2.dp.
利用滚动数组节省空间,二维数组改一维.(不优化的话 ,过是能过,但是……)
维护一个上凸壳.
秒变下凸壳(后面采取此方法).
注意: 可能为 ,也就是说相邻两个决策点的 值可能相同, 要特判返回 .(不过像代码中那样除法转换成乘法就不必特判了)
Code1:滚优
#include<bits/stdc++.h>
#define ll long long
#define int ll
#define fr(i,r) for(int i=1;i<=r;++i)
#define For(i,l,r) for(int i=l;i<=r;++i)
#define Rof(i,r,l) for(int i=r;i>=l;--i)
#define pb push_back
#define pii pair<int,int>
#define mp(x,y) make_pair(x,y)
#define msec 800
using namespace std;
const int N=1e5+10,maxK=210,Inf=9223372036854775807;
char cch;
int res,zf;
inline int rd()
{
while((cch=getchar())<45);
if(cch^45)res=cch^48,zf=1;
else res=0,zf=-1;
while((cch=getchar())>=48)res=(res*10)+(cch^48);
return res*zf;
}
int n=rd(),kc=rd();
int S[N];
int q[N],h,t;
int dp[N][2];bool d;
int pre[N][maxK];
inline int X(int j)
{
return S[j];
}
inline int Y(int j)
{
return -dp[j][d^1]+S[j]*S[j];
}
inline int K(int i)
{
return S[i];
}
inline int Fi(int i,int j)
{
return dp[j][d^1]+(S[i]-S[j])*S[j];
}
inline int deltaX(int k1,int k2)
{
return X(k2)-X(k1);
}
inline int deltaY(int k1,int k2)
{
return Y(k2)-Y(k1);
}
inline bool Kjud1(int i)
{
return bool(deltaY(q[h],q[h+1])<=K(i)*deltaX(q[h],q[h+1]));
}
inline bool Kjud2(int i)
{
return bool(deltaY(q[t],i)*deltaX(q[t-1],q[t])<=deltaY(q[t-1],q[t])*deltaX(q[t],i));
}
signed main()
{
fr(i,n) S[i]=S[i-1]+rd();
fr(j,kc)
{
d^=1;//滚动数组
h=0,t=0;
fr(i,n)
{
while(h<t&&Kjud1(i)) ++h;
dp[i][d]=Fi(i,q[h]);
pre[i][j]=q[h];//标记前i个数切第j刀的最优位置
while(h<t&&Kjud2(i)) --t;
q[++t]=i;
}
}
cout<<dp[n][d]<<'\n';
int pos=n;
Rof(i,kc,1)pos=pre[pos][i]/*返回了上次切的位置*/,cout<<pos<<' ';
return 0;
}
Code2:无滚优
#include<bits/stdc++.h>
#define ll long long
#define int ll
#define fr(i,r) for(int i=1;i<=r;++i)
#define For(i,l,r) for(int i=l;i<=r;++i)
#define Rof(i,r,l) for(int i=r;i>=l;--i)
#define pb push_back
#define pii pair<int,int>
#define mp(x,y) make_pair(x,y)
#define msec 800
using namespace std;
const int N=1e5+10,maxK=210,Inf=9223372036854775807;
char cch;
int res,zf;
inline int rd()
{
while((cch=getchar())<45);
if(cch^45)res=cch^48,zf=1;
else res=0,zf=-1;
while((cch=getchar())>=48)res=(res*10)+(cch^48);
return res*zf;
}
int n=rd(),kc=rd();
int S[N];
int q[N],h,t;
int dp[N][maxK],d;
int pre[N][maxK];
inline int X(int j)
{
return S[j];
}
inline int Y(int j)
{
return -dp[j][d-1]+S[j]*S[j];
}
inline int K(int i)
{
return S[i];
}
inline int Fi(int i,int j)
{
return dp[j][d-1]+(S[i]-S[j])*S[j];
}
inline int deltaX(int k1,int k2)
{
return X(k2)-X(k1);
}
inline int deltaY(int k1,int k2)
{
return Y(k2)-Y(k1);
}
inline bool Kjud1(int i)
{
return bool(deltaY(q[h],q[h+1])<=K(i)*deltaX(q[h],q[h+1]));
}
inline bool Kjud2(int i)
{
return bool(deltaY(q[t],i)*deltaX(q[t-1],q[t])<=deltaY(q[t-1],q[t])*deltaX(q[t],i));
}
signed main()
{
fr(i,n) S[i]=S[i-1]+rd();
fr(j,kc)
{
++d;
h=0,t=0;
fr(i,n)
{
while(h<t&&Kjud1(i)) ++h;
dp[i][d]=Fi(i,q[h]);
pre[i][j]=q[h];//标记前i个数切第j刀的最优位置
while(h<t&&Kjud2(i)) --t;
q[++t]=i;
}
}
cout<<dp[n][d]<<'\n';
int pos=n;
Rof(i,kc,1)pos=pre[pos][i]/*返回了上次切的位置*/,cout<<pos<<' ';
return 0;
}
T7 P
题目描述:
思路:
Code:
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!