zoukankan      html  css  js  c++  java
  • 决策单调性 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
    ",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
    ",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-yj+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
    ",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
    ",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
    ",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
    ",ans);
    }
    View Code

    bzoj1044 木棍分割

    题目大意:给定一些木棍,首尾顺次相连,最多切m次,使最长的一段最小的值和方案数。

    思路:第一问二分答案,然后贪心的能选就选不能选就开一个新的或者返回不行,分的段过多也是不行的;第二问dp一下,f[i][j]表示前j个木棍分i段的方案数(这里的i段一定是每段都不为空,答案中取f[i][n]的和就可以了),f[i][j]=sigma f[i-1][k](sum[j]-sum[k]<=ans1),这里因为是前缀和所以决策一定单调,保存一下这个决策更新的起点,对f[i]做一个前缀和就可以求出答案了,最后复杂度是O(nm)的。因为空间给的很小,所以要滚动数组。(第二问的方案中一定有ans1这个长度的一段,因为如果不能分出这样的一段,肯定是每一段都比这个小(大的不会更新进答案),那么ans1就会变小,而不是这个了。)

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #define maxnode 50005
    #define p 10007
    using namespace std;
    int ai[maxnode]={0},sum[maxnode]={0},f[2][maxnode]={0},g[2][maxnode]={0},n,m;
    bool judge(int d)
    {
        int i,ss=0,tot=0;
        for (i=1;i<=n;++i)
        {
            if (ss+ai[i]<=d){ss+=ai[i];}
            else {++tot;if (ai[i]>d) return false; ss=ai[i];}
        }
        if (tot>m) return false;
        else return true;
    }
    int main()
    {
        freopen("stick.in","r",stdin);
        freopen("stick.out","w",stdout);
        
        int i,j,k,l,r,mid,ans=0;
        scanf("%d%d",&n,&m);
        for (i=1;i<=n;++i) 
        {
           scanf("%d",&ai[i]);sum[i]=sum[i-1]+ai[i];
        }
        l=ai[1];r=sum[n];
        while(l!=r)
        {
            mid=(l+r)/2;
            if (judge(mid)) r=mid;
            else l=mid+1;
        }
        printf("%d ",l);r=0;
        for (i=1;i<=n;++i)
        {
            if (sum[i]<=l) f[0][i]=1;
            g[0][i]=(g[0][i-1]+f[0][i])%p;
        }
        for (i=2;i<=m+1;++i)
        {
            k=0;r^=1;g[r][0]=0;
            for (j=1;j<=n;++j)
            {
                while(sum[j]-sum[k]>l) ++k;
                k=max(0,k-1);
                f[r][j]=((g[r^1][j-1]-g[r^1][k])%p+p)%p;
                g[r][j]=(g[r][j-1]+f[r][j])%p;
            }
            ans=(ans+f[r][n])%p;
        }
        printf("%d
    ",ans);
        
        fclose(stdin);
        fclose(stdout);
    }
    View Code
  • 相关阅读:
    this.$router.push({})实现路由跳转
    JavaScript 实用小技巧
    js中如何实现数组去重--面试题
    面试中知识点积累
    从输入url到网页呈现的过程
    ptyhon学习---作业day1
    python学习---for循环
    python学习---while循环
    python学习---if else流程判断
    ajax最基础入门
  • 原文地址:https://www.cnblogs.com/Rivendell/p/4531567.html
Copyright © 2011-2022 走看看