一道bz1000题的斜率优化动态规划(蒟蒻的内心独白)
由于double判断的时候好像写的有点问题一直wa最后用整形就对了 血的教训
首先我们可以得到dp方程dp[i]=min(dp[j]+(sigma(i->j)+i-(j+1)-l))^2
dp[i]=dp[j]+(sum[i]-sum[j]+i-j+l+1)^2
令c=l+1;
s[i]=sum[i]+i;
可得dp[i]=dp[j]+(s[i]-s[j]+c)^2;
将平方展开得s[i]^2+s[j]^2+c^2-2*s[i]*c+2*s[j]*c-2*s[i]s[j];
此时的做法就比较套路了
对于每一个对应的i s[i]^2+c^2-2*s[i]*c均属于常数级别
假设有另一个k使得k比j更优
扫描二维码关注公众号,回复:
1041113 查看本文章
令f[i]=dp[i]+s[i]^2+s[i]*c
那么f[k]-2*s[i]s[k]<=f[j]-2*s[i]s[j];
移项得f[k]-f[j]<=2*s[i]*(s[k]-s[j])
2*s[i]>=(f[k]-f[j])/(s[k]-s[j])
s[i]随着i的增大而增大
那么随着一个i的成立,后面所有的i都成立,那么我们就可以用这个东西来做一个o(n)的斜率优化了
粘上代码
#include<cstdio> #include<cstring> #include<algorithm> #include<iostream> using namespace std; const int maxn=50005; long long sum[maxn],s[maxn],dp[maxn]; int n,l,c[maxn],q[maxn]; long long pf(int x) { return x*x; } double xielv(int j,int k) { return (dp[k]-dp[j]+(s[k]+l+1)*(s[k]+l+1)-(s[j]+l+1)*(s[j]+l+1))/(2.0*(s[k]-s[j])); } int main() { scanf("%d%d",&n,&l); for (int i=1;i<=n;i++) { scanf("%lld",&c[i]); sum[i]=sum[i-1]+c[i]; } for (int i=1;i<=n;i++) s[i]=sum[i]+i; int head=1; int tail=0; q[++tail]=0; for (int i=1;i<=n;i++) { while (head<tail&&xielv(q[head],q[head+1])<=s[i]) head++; dp[i]=dp[q[head]]+(s[i]-s[q[head]]-l-1)*(s[i]-s[q[head]]-l-1); while (head<tail&&xielv(q[tail],i)<xielv(q[tail-1],q[tail])) tail--; q[++tail]=i; } printf("%lld\n",dp[n]); return 0; }
据说这道题n(logn)的做法比o(n)的要跑的快啊
主要是常数带的比较大差别不多
问题稍微有点大啊
表示这个真的有点难推
o(nlogn)的做法主要是一个决策单调性,对于这个问题可以打表肉眼观察也可以证明然后可以二分的找到栈中第一个满足的一个转折点,对于每一个点只进栈出栈一次,二分的时间复杂度为o(nlogn)这里代码就不上了
:)