首先看出是DP
方程 dp[i]=min{dp[j]+(sum[i]-sum[j])^2+m}.
n的范围是<=500000,二维动归会超时。
那末,下面是斜率优化:
在做状态i的时候,我们会比较在i前面的状态j和状态k
我们假设j>k;
那末,如果状态j更优,就有(dp[j]+(sum[i]-sum[j])^2)>(dp[k]+(sum[i]-sum[k]))
经移项得((dp[j]+sum[j]^2)-(dp[k]+sum[k]^2))/(2*sum[j]^2-2*sum[k])<sum[i]
设dp[j]+sum[j]^2=y1,dp[k]+sum[k]^2=y2,2*sum[j]^2=x1,2*sum[k]^2=x2,把点(x1,y1)和(x2,y2)投射到坐标系中,上面的式子就是过这两点直线的斜率。
设这个斜率为g(j,k)。
弄一发类似单调队列的东东,存位置,维护队列使队列内相邻的点斜率单调上升。
取数时,先取出头指针和下一个位置的斜率,如果斜率比sum[i]小,那末说明这时取后一个数更优,头指针后移。
进队列时,比较尾指针和前一个位置的斜率与需要加入的点和尾指针的斜率,如果前者较大,说明加入了状态i后,不满足斜率递增,那末尾指针前移。
时间复杂度降为O(n)。
代码:
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
ll dp[500000+10],sum[500000+10],dui[500000+10];
int n,m,i,j,a,tou,wei;
ll up(int x,int y)//y1-y2
{
int j=x,k=y;
return dp[j]+sum[j]*sum[j]-dp[k]-sum[k]*sum[k];
}
ll down(int x,int y)//x1-x2
{
int j=x,k=y;
return 2*(sum[j]-sum[k]);
}
int main()
{
while(scanf("%d%d",&n,&m)==2)
{
memset(sum,0,sizeof(sum));
for(i=1;i<=n;++i)
{
scanf("%d",&a);
sum[i]=sum[i-1]+a;
}
memset(dui,0,sizeof(dui));
memset(dp,0,sizeof(dp));
tou=0;
wei=1;
dui[1]=1;
dp[1]=sum[1]*sum[1]+m;
for(i=2;i<=n;++i)
{
while(tou<wei&&up(dui[tou+1],dui[tou])<=sum[i]*down(dui[tou+1],dui[tou]))
++tou;<span style="white-space:pre"> </span>//后面的状态更优,后移前指针
j=dui[tou];
dp[i]=dp[j]+m+(sum[i]-sum[j])*(sum[i]-sum[j]);//状态转移
while(tou<wei&&up(i,dui[wei])*down(dui[wei],dui[wei-1])<=up(dui[wei],dui[wei-1])*down(i,dui[wei]))
wei--;<span style="white-space:pre"> </span>//不满足斜率递减,前移尾指针
dui[++wei]=i;//加入状态i
}
printf("%lld\n",dp[n]);
}
return 0;
}C++新手,代码又丑又LOW不喜勿喷。。。

本文介绍了一种在动态规划问题中使用斜率优化的方法,通过维护一个单调队列来实现状态转移,从而将时间复杂度从O(n^2)降低到O(n)。这种方法适用于特定形式的状态转移方程。
1万+

被折叠的 条评论
为什么被折叠?



