决策单调性 dp

对于一些具有决策单调性的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]);

}
View Code

 

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]);

}
View Code

 

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]);

}
View Code

 

bzoj3675 序列分割

题目大意:一个n个数的序列,分k次,每次从一个位置分开后,将两边的子段和的乘积加给ans,求ans的最大值。

思路:看到这道题目想做个四重的循环,后来就开始优化。我们可以得到dp[i,m]=dp[j,m-1]+sum[j]*(sum[i]-sum[j])(这里倒着考虑比较好想),然后进行斜率优化。这道题目中可能出现分母为0的情况,就赋成正负无穷就可以了。

#include<iostream>

#include<cstdio>

#include<cstring>

#define maxnode 100005

#define inf 0x7fffffffffffffffLL

using namespace std;

long long dp[maxnode][2]={0},q[maxnode]={0},sum[maxnode]={0};

int cur=0,n;

long long getdp(int i,int j)

{

    return dp[j][cur^1]+sum[j]*(sum[i]-sum[j]);

}

long long getup(int j,int k)

{

    return (dp[j][cur^1]-sum[j]*sum[j])-(dp[k][cur^1]-sum[k]*sum[k]);

}

long long getdown(int j,int k)

{

    return sum[j]-sum[k];

}

double g(int j,int k)

{

    long long i,jj;

    i=getdown(j,k);jj=getup(j,k);

    if (i==0)

    {

        if (jj>0) return inf;

        if (jj==0) return 0;

        if (jj<0) return -inf;

    }

    else return jj*1.0/i;

}

int main()

{

    int h,t,i,j,k;

    scanf("%d%d",&n,&k);

    for (i=1;i<=n;++i)

    {

        scanf("%lld",&sum[i]);

        sum[i]+=sum[i-1];

    }

    for (i=1;i<=k;++i)

    {

        cur^=1;h=t=1;q[t]=0;

        for (j=1;j<=n;++j)

        {

            while(h<t&&g(q[h+1],q[h])>-sum[j]) ++h;

            dp[j][cur]=getdp(j,q[h]);

            while(h<t&&g(j,q[t])>g(q[t],q[t-1])) --t;

            q[++t]=j;

        }

    }

    printf("%lld\n",dp[n][cur]);

}
View Code

 

 bzoj1096 [ZJOI2007] 仓库建设

题目大意:山坡上有n个工厂,从山上到山脚依次是1-n,到第一个工厂的距离为xi,每个工厂有产品pi个,从一个工厂建仓库的钱是ci。每个工厂的产品只能向下运到某一个仓库,每一个产品一单位长度的运费是1。求所有产品都能进仓库的最小费用(建仓库的费用和运费总和)。

思路:首先每个产品一定运到离它下面最近的那个仓库费用最小,同时第n个位置一定要建仓库。我们用f[i]表示第i个位置建仓库的总费用,ss[j,i]表示j到i的产品运到n的运费,ss[j,i]=sigma(k=j~i)((xi[n]-xi[k])*pi[k]),设sum[k]=(xi[n]-xi[k])*pi[k],可以用前缀和的思想维护出来,ge[i,j]表示i~j的产品个数和,f[i]=f[j]+ci[i]+ss[j+1,i]-ge[j+1,i]*(xi[n]-xi[i]),j<k时,dp[j,i]>dp[k,i],化简可得(dp[j]-dp[k]-sum[j]+sum[k])/(ge[k]-ge[j])>(xi[n]-xi[i]),然后就可以斜率优化了。

#include<iostream>

#include<cstdio>

#include<cstring>

#include<algorithm>

#define maxnode 1000005

using namespace std;

long long fi[maxnode]={0},xi[maxnode]={0},pi[maxnode]={0},ci[maxnode]={0},

          sum[maxnode]={0},hdis[maxnode]={0},q[maxnode]={0},n;

long long getdp(int i,int j)

{

    return (fi[j]+ci[i]-(xi[n]-xi[i])*(sum[i]-sum[j])+hdis[i]-hdis[j]);

}

long long getup(int i,int j){return (fi[i]-fi[j]-hdis[i]+hdis[j]);}

long long getdown(int i,int j){return (sum[j]-sum[i]);}

double g(int i,int j){return (getup(i,j)*1.0)/(getdown(i,j));}

int main()

{

    int m,i,j,head,tail;

    scanf("%d",&n);

    for (i=1;i<=n;++i) 

    {

      scanf("%I64d%I64d%I64d",&xi[i],&pi[i],&ci[i]);sum[i]=sum[i-1]+pi[i];

    }

    for (i=1;i<=n;++i) hdis[i]=hdis[i-1]+(xi[n]-xi[i])*pi[i];

    head=tail=1;q[tail]=0;

    for (i=1;i<=n;++i)

    {

        while(head<tail&&g(q[head+1],q[head])>(1.0*(xi[n]-xi[i]))) ++head;

        fi[i]=getdp(i,q[head]);

        while(head<tail&&g(i,q[tail])>g(q[tail],q[tail-1])) --tail;

        q[++tail]=i;

    }

    printf("%I64d\n",fi[n]);

}
View Code

 

 

还有一种降至O(nlogn)的方法,穷举每一个数的时候,二分这个决策开始的转折点,然后更新决策。这个过程用栈实现。

某个模拟赛的题目

题目大意:给定一些区间,求出一些(多于一个)区间使它们的交与并的乘积最大。

思路:我们将被包含的区间都去掉(同时要更新答案,否则可能会丢掉答案),然后我们得到了左右端点都严格递增的一些线段,进行dp优化,就可以了。(这是在我们假定决策单调性的情况下。)

当时的题解说有决策单调性,但是写的时候发现并不能a掉,特判了小数据才a。希望有大神能够解答一下。

#include<iostream>

#include<cstdio>

#include<algorithm>

#include<cstring>

#define maxnode 1000010

using namespace std;

struct use{

    long long ll,rr;

}aa[maxnode]={0};

struct zhan{

    int l,r,jue;

}zz[maxnode]={0};

long long ans=0;

int n,m;

int my_comp(const use x,const use y)

{

    if (x.ll==y.ll)

    {

        if (x.rr>y.rr) return 1;

        else return 0;

    }

    if (x.ll<y.ll) return 1;

    else return 0;

}

long long calc(int i,int j)

{

    return ((aa[j].rr-aa[i].ll)*(aa[i].rr-aa[j].ll));

}

int find(int x,int i)

{

    int l,r,mid;

    if (calc(i,zz[x].r)<calc(zz[x].jue,zz[x].r)) return zz[x].r+1;

    l=zz[x].l;r=zz[x].r;

    while(l<r)

    {

        mid=(l+r)/2;

        if (calc(i,mid)>calc(zz[x].jue,mid)) r=mid;

        else l=mid+1;

    }

    return l;

}

void dp()

{

    int i,j,tot=0,now=0;

    long long ff;

    zz[now=++tot]=(zhan){1,n,1};

    for (i=2;i<=n;++i)

    {

        if (zz[now].r<i) ++now;

        ff=calc(zz[now].jue,i);

        ans=max(ans,ff);

        if (calc(zz[tot].jue,n)<calc(i,n))

        {

          while(tot>=now)

          {

            if (calc(zz[tot].jue,zz[tot].l)<calc(i,zz[tot].l)) --tot;

            else

            {

                int k=find(tot,i);

                zz[tot].r=k-1;zz[++tot]=(zhan){k,n,i};

                break;

            }

          }

        }

    }

}

long long calc1(int i,int j)

{

    return (aa[j].rr-aa[i].ll)*(aa[i].rr-aa[j].ll);

}

void work()

{

    int i,j;

    for (i=1;i<n;++i)

      for (j=i+1;j<=n;++j) ans=max(ans,calc1(i,j));

}

int main()

{    

    int i,j,tot;

    scanf("%d",&n);

    for (i=1;i<=n;++i) scanf("%I64d%I64d",&aa[i].ll,&aa[i].rr);

    sort(aa+1,aa+n+1,my_comp);tot=1;

    for (i=2;i<=n;++i)

    {

        if (aa[i].rr>aa[tot].rr) aa[++tot]=aa[i];

        else ans=max(ans,(aa[tot].rr-aa[tot].ll)*(aa[i].rr-aa[i].ll));

    }

    n=tot;

    if (n<3000) work();

    else dp();

    printf("%I64d\n",ans);

}
View Code

 

你可能感兴趣的:(dp)