2018-Summer之区间dp

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u011469138/article/details/82292285

区间dp

什么是区间dp:
区间dp是解决区间上的一类动态规划问题的方法,例如区间合并、区间符号配对等等,其思想是将大区间分成小区间,结合动态转移方程来求小区间的最优解,最终合并成大区间的最优解

区间dp的一般思路
1.确定dp数组初始值
2.确定转移方程
3.枚举区间、分割点
4.dp[1][N]一般是最后答案
5.区间dp的复杂度一般为n^2,数据规模为几百

区间dp的一般模板:

int dp[maxn][maxn];
void init()
{
    for(int i=1;i<=n;i++)
        dp[i][i]=INF/0;//此处初始值视题目而定
}
for(int len=1;len<=n;len++)//枚举区间长度
{
    for(int i=1;i+len<=n;i++)//枚举区间起点
        {
        int j=len+i;//区间终点
        for(int k=i;k<j;k++)//枚举区间分割点
            dp[i][j]=max(dp[i][j],dp[i][k]+dp[k+1][j]+w[i][j]);
        }
}

例题:https://vjudge.net/problem/HRBUST-1818

石子合并问题-直线版

给一行石子,两两相邻合并成新石子所得为得分,求最终得分最小和最大值

解题思路比较明了,区间i~j的转移方程为

最大值:
dp[n][n]初始化为0
dp[i][j]=max(dp[i][j],dp[i][k]+dp[k+1][j]+sum[j]-sum[i-1]);

最小值:
dp[n][n]初始化为0(区间长度为1的情况)
dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]+sum[j]-sum[i-1]);

#include<cstdio>
#include<iostream>
#include<cstring>
#define INF 0x3f3f3f3f
using namespace std;
int st[105];
int sum[105]={0};
int dp[205][205];
int dp2[205][205];
int main()
{
    int n;
    while(~scanf("%d",&n))
    {
        for(int i=1;i<=n;i++)
            {
            scanf("%d",&st[i]);
            sum[i]=sum[i-1]+st[i];
            }
            memset(dp,0,sizeof(dp));
            memset(dp2,INF,sizeof(dp2));
        for(int i=1;i<=n;i++)
        {
            dp[i][i]=0;
            dp2[i][i]=0;    

        }       

        for(int len=1;len<n;len++)
            for(int i=1;i+len<=n;i++)
                {
                    int j=i+len;
                    for(int k=i;k<j;k++)
                        dp[i][j]=max(dp[i][j],dp[i][k]+dp[k+1][j]+sum[j]-sum[i-1]);
                }

        for(int len=1;len<n;len++)
            for(int i=1;i+len<=n;i++)
                {
                    int j=i+len;

                    for(int k=i;k<j;k++)
                        dp2[i][j]=min(dp2[i][j],dp2[i][k]+dp2[k+1][j]+sum[j]-sum[i-1]);
                }

        cout<<dp2[1][n]<<' '<<dp[1][n]<<endl;

    }
    return 0;

}

石子合并问题–圆形版

https://vjudge.net/problem/HRBUST-1819
这个问题和前一个差不多,只不过是由直线变成了圆形,直线变圆形的解决方案一般是把直线变成两倍

#include <algorithm>
#include <iostream>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define INF 0x3f3f3f3f
using namespace std;
int a[210];
int dp[500][500];
int sum[210];
int main(){
    int n;
    while(~scanf("%d", &n)){
        memset(dp, INF, sizeof(dp));
        for(int i=1; i<=n; i++){
            scanf("%d", &a[i]);
            a[i+n]=a[i];
            dp[i][i]=0;
            dp[i+n][i+n]=0;
        }
        sum[0]=0;
        for(int i=1; i<=n*2; i++){
            sum[i]=sum[i-1]+a[i];
        }
        for(int len=2; len<=n; len++){
            for(int i=1; i+len-1<=2*n; i++){
                int j=i+len-1;
                for(int k=i; k<j; k++){
                    dp[i][j]=min(dp[i][j], dp[i][k]+dp[k+1][j]+sum[j]-sum[i-1]);
                }
            }
        }
        int ans=INF;    
        for(int i=1; i<=n; i++){
            ans=min(ans, dp[i][i+n-1]);
        }
        cout << ans << ' ';
        memset(dp, 0, sizeof(dp));
        for(int len=2; len<=n; len++){
            for(int i=1; i+len-1<=2*n; i++){
                int j=i+len-1;
                for(int k=i; k<j; k++){
                    dp[i][j]=max(dp[i][j], dp[i][k]+dp[k+1][j]+sum[j]-sum[i-1]);
                }
            }
        }
        ans=0;
        for(int i=1; i<=n; i++){
            ans=max(ans, dp[i][i+n-1]);
        }
        cout << ans << endl;
    }
    return 0;
}

括号配对

https://vjudge.net/problem/POJ-2955
题意是给一行符号,其中()是成对的,【】是成对的,问最多有多少对配对成立
思路是枚举区间,区间两头配对则加1

#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
char s[300];
int dp[500][500]; 
char t[]={"end"};
int main()
{

    while(~scanf("%s",s)&&(strcmp(s,t)!=0))
    {   
        int l=strlen(s);
        memset(dp,0,sizeof(dp));
        for(int len=0;len<l;len++)
            for(int i=0;i+len<l;i++)
            {
                int j=i+len;
                if((s[i]=='('&&s[j]==')')||(s[i]=='['&&s[j]==']'))
                    dp[i][j]=dp[i+1][j-1]+2;
                for(int k=i;k<j;k++)
                    dp[i][j]=max(dp[i][j],dp[i][k]+dp[k+1][j]);

            }

        cout<<dp[0][l-1]<<endl; 
    }
return 0;
}

猜你喜欢

转载自blog.csdn.net/u011469138/article/details/82292285