标签:
对于一些具有决策单调性的dp题目,我们可以应用斜率优化将复杂度从O(n^2)降到O(n)。
bzoj1010 HNOI2008 玩具装箱toy
题目大意:对于一些一维长度的物品,我们可以将连续的i~j个物品放在一起,费用是(j-i+sigma lk(i<=k<=j)-L)^2,求n个物品最小费用。
思路:对于这类dp题目,我们可以通过进行一系列数学化简,找出其中的斜率,进而斜率优化。我们设sum[i]表示前i件物品的长度和,s[i]为sum[i]+i,dp[i]=dp[j]+(s[i]-s[j]-L)^2。我们取j<k,dp[j,i]-dp[k,i]>0,带入之前的式子,将L+1,将含i的移到右边,含j、k的移到另一边,就会得到((dp[k,i]+sk^2)-(dp[j,i]+sj^2))/(sk-sj)<2(si-L)①,我们设左边=w[k,j],显然当w[k,j]<右边的时候,k更优。对于j<k<i,w[i,k]<w[k,j]②时,k不可能是之后用到的最优解,所以可以舍弃。我们维护可能用到的最优解,不断的舍弃那些不好的解,就能将复杂度降至O(n)。对于这里涉及两个符号的问题,我们可以统一符号(如上两个式子)。
#include<iostream> #include<cstdio> #include<cstring> #define maxnode 50005 using namespace std; long long dp[maxnode]={0},sum[maxnode]={0},q[maxnode]={0},l; long long getup(int j,int k) { return dp[j]+(sum[j])*(sum[j])-(dp[k]+(sum[k])*(sum[k])); } long long getdown(int j,int k) { return (sum[j]-sum[k]); } long long getdp(int i,int j) { return dp[j]+(sum[i]-sum[j]-l)*(sum[i]-sum[j]-l); } double g(int j,int k) { return getup(j,k)*1.0/(2.0*getdown(j,k)); } int main() { int n,i,j,head,tail; scanf("%d%lld",&n,&l); for (i=1;i<=n;++i) { scanf("%lld",&sum[i]); sum[i]+=sum[i-1]; } for (i=1;i<=n;++i) sum[i]+=(long long)i; head=tail=1;q[tail]=0;++l; for (i=1;i<=n;++i) { while(head<tail&& g(q[head+1],q[head]) < 1.0*(sum[i]-l)) ++head; dp[i]=getdp(i,q[head]); while(head<tail&& g(i,q[tail]) < g(q[tail],q[tail-1])) --tail; q[++tail]=i; } printf("%lld\n",dp[n]); }
bzoj1911 APIO2010特别行动队
题目大意:n名士兵,连续的一组士兵的战斗力是他们最初战斗力和x的一个二次函数,重新编队后求最大战斗力。
思路:同上,进行化简。显然dp[j,i]=dp[j]+a(sum[i]-sum[j])^2+b(sum[i]-sum[j])+c,取j<k,dp[j,i]-dp[k,i]<0,化简后有((dpk+asumk^2)-(dpj+asumj^2))/(sumk-sumj)>(2asumi+b)。然后根据符号统一原则,就可以得到g(q[head+1],q[head])>(2asumi+b),g(i,que[tail])>g(que[tail],que[tail-1])。
#include<iostream> #include<cstdio> #include<cstring> #define maxnode 1000005 using namespace std; long long dp[maxnode]={0},sum[maxnode]={0},q[maxnode]={0},a,b,c; long long getdp(int i,int j) { return dp[j]+a*(sum[i]-sum[j])*(sum[i]-sum[j])+b*(sum[i]-sum[j])+c; } long long getup(int j,int k) { return dp[j]+a*sum[j]*sum[j]-(dp[k]+a*sum[k]*sum[k]); } long long getdown(int j,int k) { return sum[j]-sum[k]; } double g(int j,int k) { return (getup(j,k)*1.0)/(getdown(j,k)); } int main() { int i,j,n,head,tail; scanf("%d%lld%lld%lld",&n,&a,&b,&c); sum[0]=0; for (i=1;i<=n;++i) { scanf("%lld",&sum[i]); sum[i]+=sum[i-1]; } head=tail=1;q[tail]=0; for (i=1;i<=n;++i) { while(head<tail&&g(q[head+1],q[head])>(sum[i]*2.0*a+b)) ++head; dp[i]=getdp(i,q[head]); while(head<tail&&g(i,q[tail])>g(q[tail],q[tail-1])) --tail; q[++tail]=i; } printf("%lld\n",dp[n]); }
bzoj1597 土地购买
题目大意:n块土地,购买其中i块土地的代价是max(xi)*max(yi),求买下n块土地的最小价值。
思路:首先,我们对于i、j,如果xi<xj,yi<yj,那么就可以忽略i。那么我们就可以对于x升序第一关键字y降序第二关键字排列,然后对于之前的i、j忽略不计。然后就是斜率优化dp了。dp[j,i]=dp[j]+yj+1*xi。取j<k,我们可以化出(dpk-dpj)/(yk+1-yk+1)>-xi(一定要认真一步步的化式子!!!),然后符号统一一下。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define maxnode 50005 using namespace std; struct use{ int xi,yi; }area[maxnode],a[maxnode]; long long q[maxnode]={0},dp[maxnode]={0}; bool visit[maxnode]={false}; int my_comp(const use x,const use y) { if (x.xi<y.xi) return 1; if (x.xi>y.xi) return 0; if (x.yi>y.yi) return 1; return 0; } long long getdp(int i,int j) { return dp[j]+(long long)a[j+1].yi*(long long)a[i].xi; } long long getup(int j,int k) { return dp[j]-dp[k]; } long long getdown(int j,int k) { return (long long)a[j+1].yi-(long long)a[k+1].yi; } double g(int j,int k) { return getup(j,k)*1.0/getdown(j,k); } int main() { int i,j,n,head,tail,tot=0; scanf("%d",&n); for (i=1;i<=n;++i) scanf("%d%d",&area[i].xi,&area[i].yi); sort(area+1,area+n+1,my_comp); for (i=1;i<=n;++i) { j=i-1; while(j&&area[j].yi<=area[i].yi) { visit[j]=true;--j; } } for (i=1;i<=n;++i) if (!visit[i]) a[++tot]=area[i]; head=tail=1;q[tail]=0; for (i=1;i<=tot;++i) { while(head<tail&&g(q[head+1],q[head])>=-a[i].xi) ++head; dp[i]=getdp(i,q[head]); while(head<tail&&g(i,q[tail])>=g(q[tail],q[tail-1])) --tail; q[++tail]=i; } printf("%lld\n",dp[tot]); }
标签:
原文地址:http://www.cnblogs.com/Rivendell/p/4531567.html