• 专题训练之区间DP


    例题:以下例题部分的内容来自https://blog.csdn.net/my_sunshine26/article/details/77141398

    一、石子合并问题

    1.(NYOJ737)http://acm.nyist.edu.cn/JudgeOnline/problem.php?pid=737

    分析:我们dp[i][j]来表示合并第i堆到第j堆石子的最小代价。那么状态转移方程为dp[i][j]=min(dp[i][k]+dp[k+1][j]+w[i][j])  (s[i][j-1]<=k<=s[i+1][j])

     其中w[i][j]表示把两部分合并起来的代价,即从第i堆到第j堆石子个数的和,为了方便查询,我们可以用sum[i]表示从第1堆到第i堆的石子个数和,那么w[i][j]=sum[j]-sum[i-1].

    用s[i][j]表示区间[i,j]中的最优分割点,那么第三重循环可以从[i,j-1)优化到【s[i][j-1],s[i+1][j]】

     1 #include<cstdio>
     2 #include<cstring>
     3 #include<algorithm>
     4 using namespace std;
     5 typedef long long ll;
     6 const int maxn=210;
     7 const ll inf=1e18;
     8 ll dp[maxn][maxn];
     9 ll sum[maxn],a[maxn];
    10 int s[maxn][maxn];
    11 
    12 int main()
    13 {
    14     int n,i,j,k,x,y,z,len;
    15     while ( scanf("%d",&n)!=EOF )
    16     {
    17         for ( i=1;i<=n;i++ )
    18         {
    19             for ( j=1;j<=n;j++ ) dp[i][j]=inf;
    20             dp[i][i]=0;
    21             s[i][i]=i;
    22         }
    23         sum[0]=0;
    24         for ( i=1;i<=n;i++ ) 
    25         {
    26             scanf("%lld",&a[i]);
    27             sum[i]=a[i]+sum[i-1];
    28         }
    29         for ( len=2;len<=n;len++ )
    30         {
    31             for ( i=1;i<=n;i++ )
    32             {
    33                 j=len+i-1;
    34                 if ( j>n ) break;
    35                 for ( k=s[i][j-1];k<=s[i+1][j];k++ ) 
    36                 {
    37                     if ( dp[i][j]>dp[i][k]+dp[k+1][j]+sum[j]-sum[i-1] )
    38                     {
    39                         dp[i][j]=dp[i][k]+dp[k+1][j]+sum[j]-sum[i-1];
    40                         s[i][j]=k;
    41                     }
    42                 }
    43             }
    44         }
    45         printf("%lld
    ",dp[1][n]);
    46     }
    47     return 0;
    48 } 
    NYOJ737

    2.(HDOJ3506)http://acm.hdu.edu.cn/showproblem.php?pid=3506

    题意:上一题的升级版,将上一层的线性变成一个圈。这时候我们只需要将N变成n=2*N-1即可,最后ans=min(dp[i][i+n-1])

     1 #include<cstdio>
     2 #include<cstring>
     3 #include<algorithm>
     4 using namespace std;
     5 typedef long long ll;
     6 const int maxn=2010;
     7 const ll inf=1e18;
     8 ll dp[maxn][maxn];
     9 ll sum[maxn],a[maxn];
    10 int s[maxn][maxn];
    11 
    12 int main()
    13 {
    14     int n,i,j,k,x,y,z,len,N;
    15     ll ans;
    16     while ( scanf("%d",&N)!=EOF )
    17     {
    18         n=2*N-1;
    19         for ( i=1;i<=n;i++ )
    20         {
    21             for ( j=1;j<=n;j++ ) dp[i][j]=inf;
    22             dp[i][i]=0;
    23             s[i][i]=i;
    24         }
    25         sum[0]=0;
    26         for ( i=1;i<=N;i++ ) 
    27         {
    28             scanf("%lld",&a[i]);
    29             sum[i]=a[i]+sum[i-1];
    30         }
    31         for ( i=1;i<N;i++ ) sum[i+N]=a[i]+sum[i+N-1];
    32         for ( len=2;len<=N;len++ )
    33         {
    34             for ( i=1;i<=n;i++ )
    35             {
    36                 j=len+i-1;
    37                 if ( j>n ) break;
    38                 for ( k=s[i][j-1];k<=s[i+1][j];k++ )
    39                 {
    40                     if ( dp[i][j]>dp[i][k]+dp[k+1][j]+sum[j]-sum[i-1] )
    41                     {
    42                         dp[i][j]=dp[i][k]+dp[k+1][j]+sum[j]-sum[i-1];
    43                         s[i][j]=k;
    44                     }
    45                 }
    46             }
    47         }
    48         ans=inf;
    49         for ( i=1;i<=N;i++ )
    50         {
    51             j=i+N-1;
    52             ans=min(ans,dp[i][j]);
    53         }
    54         printf("%lld
    ",ans);
    55     }
    56     return 0;
    57 } 
    HDOJ3506

    二、括号匹配问题

    1.(POJ2955)http://poj.org/problem?id=2955

    题意:给出一个的只有'(',')','[',']'四种括号组成的字符串,求最多有多少个括号满足题目里所描述的完全匹配。

    分析:用dp[i][j]表示区间[i,j]里最大完全匹配数。只要得到了dp[i][j],那么就可以得到dp[i-1][j+1]  dp[i-1][j+1]=dp[i][j]+(s[i-1]于s[j+1]匹配?2:0).

    然后利用状态转移方程更新一下区间最优解即可。dp[i][j]=max(dp[i][j],dp[i][k]+dp[k+1][j])

     1 #include<cstdio>
     2 #include<cstring>
     3 #include<algorithm>
     4 using namespace std;
     5 typedef long long ll;
     6 const int maxn=105;
     7 char s[maxn];
     8 ll dp[maxn][maxn];
     9 
    10 int main()
    11 {
    12     int n,i,j,k,x,y,z,len;
    13     while ( scanf("%s",s+1)!=EOF && s[1]!='e' )
    14     {
    15         n=strlen(s+1);
    16         memset(dp,0,sizeof(dp));
    17         for ( len=2;len<=n;len++ )
    18         {
    19             for ( i=1;i<=n;i++ )
    20             {
    21                 j=i+len-1;
    22                 if ( j>n ) break;
    23                 if ( (s[i]=='('&&s[j]==')') || (s[i]=='['&&s[j]==']') ) dp[i][j]=dp[i+1][j-1]+2;
    24                 for ( k=i;k<j;k++ ) dp[i][j]=max(dp[i][j],dp[i][k]+dp[k+1][j]);
    25             }
    26         }
    27         printf("%lld
    ",dp[1][n]);
    28     }
    29     return 0;    
    30 } 
    POJ2955

    2.(NYOJ15)http://acm.nyist.edu.cn/JudgeOnline/problem.php?pid=15

    分析:最少添加的括号数=总括号-最大匹配的括号数,代码于上一题基本一致

     1 #include<cstdio>
     2 #include<cstring>
     3 #include<algorithm>
     4 using namespace std;
     5 typedef long long ll;
     6 const int maxn=105;
     7 char s[maxn];
     8 ll dp[maxn][maxn];
     9 
    10 int main()
    11 {
    12     int n,i,j,k,x,y,z,len,T;
    13     scanf("%d",&T);
    14     while ( T-- )
    15     {
    16         scanf("%s",s+1);
    17         n=strlen(s+1);
    18         memset(dp,0,sizeof(dp));
    19         for ( len=2;len<=n;len++ )
    20         {
    21             for ( i=1;i<=n;i++ )
    22             {
    23                 j=i+len-1;
    24                 if ( j>n ) break;
    25                 if ( (s[i]=='('&&s[j]==')') || (s[i]=='['&&s[j]==']') ) dp[i][j]=dp[i+1][j-1]+2;
    26                 for ( k=i;k<j;k++ ) dp[i][j]=max(dp[i][j],dp[i][k]+dp[k+1][j]);
    27             }
    28         }
    29         printf("%lld
    ",n-dp[1][n]);
    30     }
    31     return 0;    
    32 } 
    NYOJ15

    三、整数划分问题

    1.(NYOJ746)http://acm.nyist.edu.cn/JudgeOnline/problem.php?pid=746

    分析:用dp[i][j]表示从第一位到第i位共插入j个乘号后乘积的最大值。根据区间DP的思想我们可以从插入较少乘号的结果算出插入较多乘号的结果。

     方法是当我们要放第j的乘号时枚举放的位置。状态转移方程为dp[i][j]=max(dp[i][j],dp[k][j-1]*num[k+1][i])。其中num[i][j]表示从s[i]到s[j]这段连续区间代表的数值。

     1 #include<cstdio>
     2 #include<cstring>
     3 #include<algorithm>
     4 using namespace std;
     5 typedef long long ll;
     6 const int maxn=20;
     7 ll dp[maxn][maxn];
     8 ll num[maxn][maxn];
     9 
    10 int main()
    11 {
    12     int T,n,m,i,j,k,x,y,z;
    13     char s[maxn];
    14     scanf("%d",&T);
    15     while ( T-- )
    16     {
    17         scanf("%s%d",s+1,&m);
    18         n=strlen(s+1);
    19         memset(dp,0,sizeof(dp));
    20         for ( i=1;i<=n;i++ )
    21         {
    22             num[i][i]=s[i]-'0';
    23             for ( j=i+1;j<=n;j++ ) num[i][j]=num[i][j-1]*10+s[j]-'0';
    24         }
    25         for ( i=1;i<=n;i++ ) dp[i][0]=num[1][i];
    26         for ( j=1;j<m;j++ )
    27         {
    28             for ( i=1;i<=n;i++ )
    29             {
    30                 for ( k=1;k<i;k++ ) dp[i][j]=max(dp[i][j],dp[k][j-1]*num[k+1][i]);
    31             }
    32         }
    33         printf("%lld
    ",dp[n][m-1]);
    34     }
    35     return 0;
    36 }
    NYOJ746

    习题:

    1.(HDOJ4632)http://acm.hdu.edu.cn/showproblem.php?pid=4632

    题意:给定一个字符串,求这个字符串中包含多少回文子串(子串可以不连续)

    分析;dp[i][j]表示从第i个字符到第j个字符中包含的回文子串的个数。初始化时dp[i][i]=1(因为自己本身也算做一个回文串),其他dp[i][j]=0

    dp[i][j]=(dp[i+1][j]+dp[i][j-1]-dp[i+1][j-1]+mod)%mod (容斥思想)

    if ( s[i]==s[j] ) dp[i][j]+=dp[i+1][j-1]+1

     1 #include<cstdio>
     2 #include<cstring>
     3 #include<algorithm>
     4 using namespace std;
     5 typedef long long ll;
     6 const int maxn=1010;
     7 const int mod=10007;
     8 char s[maxn];
     9 int dp[maxn][maxn];
    10 
    11 int main()
    12 {
    13     int T,i,j,k,h,n,m,ans,len;
    14     scanf("%d",&T);
    15     for ( h=1;h<=T;h++ )
    16     {
    17         scanf("%s",s+1);
    18         n=strlen(s+1);
    19         memset(dp,0,sizeof(dp));
    20         for ( i=1;i<=n;i++ ) dp[i][i]=1;
    21         for ( len=2;len<=n;len++ )
    22         {
    23             for ( i=1;i<=n;i++ )
    24             {
    25                 j=i+len-1;
    26                 if ( j>n ) break;
    27                 dp[i][j]=(dp[i][j-1]+dp[i+1][j]-dp[i+1][j-1]+mod)%mod;
    28                 if ( s[i]==s[j] ) dp[i][j]=(dp[i][j]+dp[i+1][j-1]+1+mod)%mod;
    29             }
    30         }
    31         printf("Case %d: %d
    ",h,dp[1][n]%mod);
    32     }
    33     return 0;
    34 }
    HDOJ4632

    2.(HDOJ4745)http://acm.hdu.edu.cn/showproblem.php?pid=4745

    题意:求最长非连续回文串

    分析:先将环变成链,dp[i][j]表示区间(i,j)范围内最长的非连续回文串的长度,转移时dp[i][j]=max(max(dp[i+1][j],dp[i][j-1]),x) 当s[i]==s[j]时x=dp[i+1][j-1]+2,否则x=dp[i+1][j-1]

    最后的答案在dp[i][i+N-1]中和dp[i][N-2]+1(共起点的情况)中去寻找

     1 #include<cstdio>
     2 #include<cstring>
     3 #include<algorithm>
     4 using namespace std;
     5 const int maxn=2010;
     6 int a[maxn];
     7 int dp[maxn][maxn];
     8 
     9 int main()
    10 {
    11     int T,i,j,k,N,n,m,x,y,z,ans,len;
    12     while (  scanf("%d",&N)!=EOF && N )
    13     {
    14         
    15         n=2*N-1;
    16         for ( i=1;i<=N;i++ ) scanf("%d",&a[i]);
    17         for ( i=1;i<N;i++ ) a[i+N]=a[i];
    18         memset(dp,0,sizeof(dp));
    19         for ( i=1;i<=n;i++ ) dp[i][i]=1;
    20         ans=0;
    21         for ( len=2;len<=n;len++ )
    22         {
    23             for ( i=1;i<=n;i++ )
    24             {
    25                 j=i+len-1;
    26                 if ( j>n ) break;
    27                 int x=dp[i+1][j-1];
    28                 if ( a[i]==a[j] ) x+=2;
    29                 dp[i][j]=max(max(dp[i+1][j],dp[i][j-1]),x);
    30             }
    31         }
    32         for ( i=1;i<=N;i++ ) ans=max(ans,dp[i][i+N-1]);
    33         for ( i=1;i<=N+1;i++ ) ans=max(ans,dp[i][i+N-2]+1);
    34         printf("%d
    ",ans);
    35     }
    36     return 0;
    37 }
    HDOJ4745

    3.(HDOJ2476)http://acm.hdu.edu.cn/showproblem.php?pid=2476

    推荐此博客:https://blog.csdn.net/a601025382s/article/details/12379565

    题意:给定两个字符串a和b,求最少需要对a进行多少次操作,才能将a变成b。每次操作时将a中任意一段变成任意一个字母所组成的段。

    题解:动态规划题。dp[i][j]表示a中i到j段变成b需要的最少次数。递推公式:dp[i][j]=min(dp[i][k]+dp[k+1][j])(i<=k<j)。接着就是判断分界点了,对于字符串b,只有将相同字符一起刷才能减少操作数。所以每次碰到b[i]==b[k]时,可以减少一次操作,因为刷一次[i,k]再刷[i+1,k-1]和分别刷[i,i][k,k],[i+,k,k+1]是一样的,可操作数会减少。

    注意:由于如果一段子串两端相等,会成端更新,从而改变中间子串的字符,所以处理时可假定所以a中单个字符都需要一次变化才能变成b。之后动态规划完成后再处理a和b中形同位置相同字符的情况。

    另一种理解方式:不考虑起始串,将起始串默认为空串,找出所有dp值(dp[i][j]表示i到j这段空子串转换成目标串需要的最小次数)后,再通过ans[i]来求得最小变换值。ans[i]表示前i+1长度的子串转换成目标串需要的最小次数。

     1 #include<cstdio>
     2 #include<cstring>
     3 #include<algorithm>
     4 using namespace std;
     5 const int maxn=105;
     6 const int inf=1e9;
     7 int dp[maxn][maxn],ans[maxn];
     8 char s1[maxn],s2[maxn];
     9 
    10 int main()
    11 {
    12     int i,j,k,x,y,z,n,len;
    13     while ( scanf("%s%s",s1+1,s2+1)!=EOF )
    14     {
    15         n=strlen(s1+1);
    16         memset(dp,0,sizeof(dp));
    17         for ( i=1;i<=n;i++ ) dp[i][i]=1;
    18         for ( len=2;len<=n;len++ )
    19         {
    20             for ( i=1;i<=n;i++ )
    21             {
    22                 j=i+len-1;
    23                 if ( j>n ) break;
    24                 dp[i][j]=dp[i+1][j]+1;
    25                 for ( k=i+1;k<=j;k++ )
    26                 {
    27                     if ( s2[i]==s2[k] ) dp[i][j]=min(dp[i][j],dp[i+1][k]+dp[k+1][j]);
    28                 }
    29             }
    30         }
    31         for ( i=1;i<=n;i++ ) ans[i]=dp[1][i];
    32         for ( i=1;i<=n;i++ )
    33         {
    34             if ( s1[i]==s2[i] ) 
    35             {
    36                 if ( i==1 ) ans[i]=0;
    37                 else ans[i]=ans[i-1];
    38             }
    39             else
    40             {
    41                 for ( k=1;k<=i;k++ ) ans[i]=min(ans[i],ans[k]+dp[k+1][i]);
    42             }
    43         }
    44         printf("%d
    ",ans[n]);
    45     }
    46     return 0;
    47 }
    HDOJ2476

    4.(HDOJ5115)http://acm.hdu.edu.cn/showproblem.php?pid=5115

    题意:有一排狼,每只狼有一个伤害A,还有一个伤害B。杀死一只狼的时候,会受到这只狼的伤害A和这只狼两边的狼的伤害B的和。如果某位置的狼被杀,那么杀它左边的狼时就会收到来自右边狼的B,因为这两只狼是相邻的了。求杀掉一排狼的最小代价。

    分析:因为杀死一只狼所受的伤害总数是固定所以单独记录即可,只用考虑受到旁边狼攻击的伤害。dp[i][j]表示杀死区间区内[i,j]内的狼所需要的最小代价.

    对于区间[i,j]我们只需要枚举出哪匹狼是需要最后杀死的即可。dp[i][j]=min(dp[i][k-1]+dp[k+1][j]+a[i-1]+a[j+1]) (i<k<j,边界单独考虑)

     1 #include<cstdio>
     2 #include<cstring>
     3 #include<algorithm>
     4 using namespace std;
     5 typedef long long ll;
     6 const int maxn=205;
     7 const ll inf=1e15;
     8 ll dp[maxn][maxn];
     9 int a[maxn];
    10 
    11 int main()
    12 {
    13     int T,n,m,i,j,k,h,x,y,z,len;
    14     ll sum;
    15     scanf("%d",&T);
    16     for ( h=1;h<=T;h++ )
    17     {
    18         scanf("%d",&n);
    19         sum=0;
    20         for ( i=1;i<=n;i++ ) 
    21         {
    22             scanf("%d",&x);
    23             sum+=x;
    24         }
    25         for ( i=1;i<=n;i++ ) scanf("%d",&a[i]);
    26         a[0]=a[n+1]=0;
    27         for ( i=1;i<=n;i++ )
    28         {
    29             for ( j=1;j<=n;j++ ) dp[i][j]=inf;
    30             dp[i][i]=a[i-1]+a[i+1];
    31         }
    32         for ( len=2;len<=n;len++ )
    33         {
    34             for ( i=1;i<=n;i++ )
    35             {
    36                 j=i+len-1;
    37                 if ( j>n ) break;
    38                 x=a[i-1]+a[j+1];
    39                 dp[i][j]=min(dp[i+1][j]+x,dp[i][j-1]+x);
    40                 for ( k=i+1;k<j;k++ ) dp[i][j]=min(dp[i][j],dp[i][k-1]+dp[k+1][j]+x);
    41             }
    42         }
    43         sum+=dp[1][n];
    44         printf("Case #%d: %lld
    ",h,sum);
    45     }
    46     return 0;
    47 }
    HDOJ5115

    5.(HDOJ4283)http://acm.hdu.edu.cn/showproblem.php?pid=4283

    推荐此博客:https://www.cnblogs.com/kedebug/archive/2012/12/10/2811053.html

    题意:

    给定一个序列,序列内的人有屌丝值Di,第i个人如果是第k个出场,那么他的屌丝值为Di * (k-1),  但是导演可以通过一个栈来调整序列里面人的出场顺序。

    求一个出场序列使总屌丝值最小。

    思路:导演对于这个出场顺序的影响只是一定程度上的。比如说:

    1. 第一个人第k个出场

    2. 那么要求2~k的人都要在第一个人前面出场

    3. k+1~n的人都要在k以后出场

    明白了上面的过程,就可以定义区间dp[i, j]表示区间[i, j]在相对于i为起点情况下i在第k个出场的最小屌丝总值。

    1. dp[i, j] = (k - i) * Di + dp[i+1, k]

    2. dp[i, j] += (k + 1 - i) * (sum[j] - sum[k]) + dp[k+1, j]

     1 #include<cstdio>
     2 #include<cstring>
     3 #include<algorithm>
     4 using namespace std;
     5 typedef long long ll;
     6 const int maxn=105;
     7 const ll inf=1e18;
     8 ll dp[maxn][maxn],sum[maxn];
     9 int a[maxn];
    10 
    11 int main()
    12 {
    13     int T,i,j,k,h,x,y,z,ans,len,n;
    14     scanf("%d",&T);
    15     for ( h=1;h<=T;h++ )
    16     {
    17         scanf("%d",&n);
    18         sum[0]=0;
    19         for ( i=1;i<=n;i++ )
    20         {
    21             scanf("%d",&a[i]);
    22             sum[i]=a[i]+sum[i-1];
    23         }
    24         for ( i=1;i<=n;i++ )
    25         {
    26             for ( j=i;j<=n;j++ ) dp[i][j]=inf;
    27             dp[i][i]=0;
    28         }
    29         for ( len=2;len<=n;len++ )
    30         {
    31             for ( i=1;i<=n;i++ )
    32             {
    33                 j=i+len-1;
    34                 if ( j>n ) break;
    35                 dp[i][j]=min(dp[i+1][j]+sum[j]-sum[i],dp[i+1][j]+(j-i)*a[i]);
    36                 for ( k=i+1;k<j;k++ ) 
    37                     dp[i][j]=min(dp[i][j],dp[i+1][k]+a[i]*(k-i)+dp[k+1][j]+(sum[j]-sum[k])*(k-i+1));
    38             }
    39         }
    40         printf("Case #%d: %lld
    ",h,dp[1][n]);
    41     }
    42     return 0;
    43 }
    HDOJ4283

    小结:区间DP过程大致相同,大都满足第一层循环枚举长度,第二层循环枚举起点。最内层往往有两种形式,第一种是需要在[i,j]中找一个分割点k使得将[i,j]分成[i,k]和[k+1,j]这样两个区间能够得到最优解

    第二种形式是[i,j]可以由[i,j-1]或者[i,j+1]转移过来.重要的是找出新添加的元素(可以是k或者i)与之前那个len-1长度区间的关系

  • 相关阅读:
    CSS3 3D的总结(初学者易懂)
    BAT面试算法精品课直通BAT面试算法精品课购买优惠码-牛客网
    深度学习UFLDL老教程笔记1 稀疏自编码器Ⅱ
    深度学习UFLDL老教程笔记1 稀疏自编码器Ⅰ
    算法分析之函数渐近分析
    调用WebServices报错,请求“System.Web.AspNetHostingPermission, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089”类型的权限已失败。
    很久之前写的 ,突然想放上去
    ASP.NET Core – Web API 冷知识
    ASP.NET Core C# 反射 & 表达式树 (第三篇)
    ASP.NET Core C# 反射 & 表达式树 (第二篇)
  • 原文地址:https://www.cnblogs.com/HDUjackyan/p/9123199.html
Copyright © 2020-2023  润新知