zoukankan      html  css  js  c++  java
  • 区间dp入门+例题

      区间dp作为线性dp的一种,顾名思义是以区间作为阶段进行dp的,使用它的左右端点描述每个维度,决策往往是从小状态向大状态转移中推得的。它跟st表等树状结构有着相似的原理---向下划分,向上递推。

      dp最终要求的就是推出状态转移方程,从板子中我们可以感受出来区间dp的关键在于如何找到小状态与大状态的关系。

      

    for(int i=1;i<n;i++){//区间长度 
            for(int l=1;l+i<=n;l++){//左端点 
                for(int h=l;h<l+i;h++)//枚举区间合并的分割点找到最优解 
                    //转移方程 
            }
        }
    View Code

      这样基本的板子时间复杂度会达到o(n^3),如果被卡的话通常就要从四边形不等式等状态转移的性质出发能不能找到更好的转移,优化掉冗余。dp是拿空间换取时间的搜索,只要优化掉冗余推平不是梦。(蒟蒻口胡orz~)

      刷了一点蓝书上的例题,分享一下存个档

    石子合并 

    传送门

    非常经典的区间dp题,肉眼可见dp[l][r]=minrk=l(dp[l][r],dp[l][k]+dp[k+1][r])

    #include<bits/stdc++.h>
    using namespace std;
    int dp[305][305];
    int sum[305];
    int main()
    {
        int n;scanf("%d",&n);
        memset(dp,0x3f,sizeof dp);
        for(int i=1;i<=n;i++)
            scanf("%d",&dp[i][0]),sum[i]=sum[i-1]+dp[i][0],dp[i][i]=0;
        for(int i=1;i<n;i++){//区间长度 
            for(int l=1;l+i<=n;l++){//左端点 
                for(int h=l;h<l+i;h++)//枚举区间合并的分割点找到最优解 
                    dp[l][l+i]=min(dp[l][h]+dp[h+1][l+i]+sum[l+i]-sum[l-1],dp[l][l+i]);//转移方程 
            }
        }
        cout<<dp[1][n]<<endl;
    }
    View Code

    Polygon

    传送门

    枚举删掉的第一条边,就跟上一题类似了,由于存在乘,同时维护最大和最小的状态转移一下就OK啦

    #include<bits/stdc++.h>
    using namespace std;
    char c[55][55];
    long long dp[55][55][2],num[55];
    vector <int> v,ans;
    int main()
    {
        int n;scanf("%d",&n);getchar();
        for(int i=1;i<=2*n;i++){
            if(i&1){
                int t1=i/2,t2=i/2+1;
                if(t1==0)    t1=n;
                scanf("%c",&c[t1][t2]);
                c[t2][t1]=c[t1][t2];
            }
            else    scanf("%lld",&num[i/2]),getchar();
        }
        int tot=1,mi=-1e9;
        for(int i=1;i<=n;i++){
            v.clear();v.push_back(0);
            for(int j=i;j<=n;j++)
                v.push_back(j);
            for(int j=1;j<i;j++)
                v.push_back(j);
            for(int j=1;j<=n;j++)
                for(int h=1;h<=n;h++)
                    dp[j][h][0]=-1e18,dp[j][h][1]=1e18;
            for(int j=1;j<=n;j++)    dp[j][j][0]=dp[j][j][1]=num[v[j]];
            for(int j=1;j<n;j++){
                for(int l=1;l+j<=n;l++){
                    for(int k=l;k<l+j;k++){
                        long long t1=1e18,t2=-1e18;
                        if(c[v[k]][v[k+1]]=='t')
                            t1=min(t1,dp[l][k][1]+dp[k+1][l+j][1]),
                            t2=max(t2,dp[l][k][0]+dp[k+1][l+j][0]);
                        else{
                            for(int p=0;p<2;p++)
                                for(int q=0;q<2;q++)
                                    t1=min(t1,dp[l][k][p]*dp[k+1][l+j][q]),
                                    t2=max(t2,dp[l][k][p]*dp[k+1][l+j][q]);
                        }
                        dp[l][l+j][0]=max(dp[l][l+j][0],t2);
                        dp[l][l+j][1]=min(dp[l][l+j][1],t1);
                    }
                }
            }
            if(dp[1][n][0]>mi){
                mi=dp[1][n][0];ans.clear();ans.push_back(tot);
            }
            else if(dp[1][n][0]==mi)    ans.push_back(tot);
            tot++;
        }
        cout<<mi<<endl;
        int l=ans.size();
        for(int i=0;i<l;i++)
            printf("%d%c",ans[i],i==l-1?'\n':' ');
    }
    View Code

    当然这个还有优化成o(n^3)的写法,对枚举删第一条进行优化。

    金字塔

    传送门

    阅读题。。。dp[l][r]表示s[l]-s[r]的可以构成的个数,那么枚举k为第一个子树的分割点就可以得出

      dp[l][r]=Σdp[l][k-1]+dp[k][r-1]

    #include<bits/stdc++.h>
    using namespace std;
    const int maxn=3e2+10;
    const int mod=1e9;
    char s[maxn];
    int dp[maxn][maxn];
    int main()
    {
        scanf("%s",s+1);int n=strlen(s+1);
        for(int i=1;i<=n;i++)    dp[i][i]=1;
        for(int l=n-1;l>=1;l--)
            for(int r=l+1;r<=n;r++){
                if(s[l]==s[r]){
                    for(int k=l+1;k<r;k++)
                        dp[l][r]=(dp[l][r]+1LL*dp[l][k-1]*dp[k][r-1]%mod)%mod;
                }
            }
        cout<<dp[1][n]<<endl;
    }
    View Code
  • 相关阅读:
    企业面试题|最常问的MySQL面试题集合(一)
    史上最全的大厂Mysql面试题在这里
    Linux运维必会的100道MySql面试题之(一)
    mysql数据库基础命令(一)
    MySQL基础入门之常用命令介绍
    MySQL数据库主从同步实战过程
    MySQL数据库入门备份数据库
    MySQL数据库入门多实例配置
    MySQL数据库入门常用基础命令
    运维LVS三种模式十种调度算法
  • 原文地址:https://www.cnblogs.com/r138155/p/12626554.html
Copyright © 2011-2022 走看看