区间dp

学习情况:

最近学习了区间dp,感觉这个dp比普通的dp稍微难了一点。也可能是我看的题目还比较少,所以对这个内容的理解还不是非常透彻(要达到透彻也不是我看这几个题能达到的)。

现在我也就会一点例题的微微微改,一旦改动大了一点点,我就不知道该怎么办。这次的区间dp给我的感觉就是套公式,但是最重要的就是公式中间对内容的应用。其实上课之前看了几个过去的例题,感觉还是有点意思的,但是要掌握自己独立写出代码还是有点难度。

总感觉最近上课听讲越来越难以专注了,不知道是为什么。可能是太晚睡觉?果然还是尽量少熬夜的好。不只是C++,别的科目我也不想落下,所以生活艰辛呀!一定要努力!

区间dp基本内容

区间dp顾名思义就是在区间上动态规划。一般是根据问题设出一个表示状态的dp,可以是二维的也可以是三维的,一般情况下为二维,用来表示从i到j这段区间上的最优值。

dp[i][j]可以由子区间的状态转移而来,关键是dp[i][j]表示的是什么,然后去找dp[i][j]和子区间的关系。并且在求dp[i][j]之前,i到j之间的子区间都已经求出来最优解。 这就是说,由小区间的最优解递推出大区间的最优解。

有了前面的动态规划铺垫之后,这个应该比以前的稍稍好理解一点点。但是状态转移那里总感觉更难了呢……可能是我有点放弃思考了。

无脑代码:

//一般区间DP实现代码
memset(dp, 0x3f, sizeof(dp)); //求最小值时的赋值,若求最大值赋值为0
for (int i = 1; i <= n; i++) //区间长度为1的初始化
    dp[i][i] = 0;
for (int len = 2; len <= n; len++) //枚举区间长度
{
   for (int i = 1, j = len; j <= n; i++, j++) //区间[i,j]
    {
        //DP方程实现
        for(int k=i;k<=j;k++)
        {
            
        }
    }
}

比较经典的例题:

(1)。有N堆石子排成一排,每堆石子有一定的数量。现要将N堆石子并成为一堆。合并的过程只能每次将相邻的两堆石子堆成一堆,每次合并花费的代价为这两堆石子的和,经过N-1次合并后成为一堆。求出总的代价最小值。

如果我们有n堆石头,合并的最后一次操作一定是从两堆合并成一堆:

例如3堆,花费为min(a[1] + a[2], a[2] + a[3]) + sum[3];//sum[3]表示三堆总的花费,无论怎么摆都是不变的!所以这样加

规定dp[i][j]为合并第i堆到第j堆的最小花费 
所以DP方程为: 

dp[i][j] = min(dp[i][k] + dp[k+1][j]) + sum[j] - sum[i-1] (i <= k < j)

完整代码:

memset(dp, 0x3f, sizeof(dp));//求最小值
for (int i = 1; i <= n; i++) 
    dp[i][i] = 0; 
for (int len = 2;len<=n;len++)//区间长度
{
    for (int i =1,j=len;j<=n;i++,j++)//区间一点点挪动,每个区间都试试是否满足
    {
        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]);//sum是为了加上这个区间最后所需要的一堆体力                         
        }
    }
}
cout<<dp[1][n]<<endl;//从1到n这个大区间的最小值

(2)。给出一串的只有‘(’ ‘)’  '[‘  ']'四种括号组成的串,让你求解需要最少添加括号数让串中的所有括号完全匹配。dp[i][j]表示i到j这段的最大匹配度,检测a[i]与a[j]是否匹配,匹配的话dp[i][j]=dp[i+1][j-1]+2,否则任意分为两个区间按第一种模型处理。

代码:

int main()
{
    string s;
    while(cin>>s&&s!="end")
    {
        memset(dp,0,sizeof(dp)); //需要匹配度最大值
        for(int l=1;l<s.size();l++)//因为字符串从a[0]开始,不是我熟悉的a[1],所以跟我一样的同学要注意一下这个地方。
        {
            for(int i=0,j=l;j<s.size();j++,i++)
            {
                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][k]+dp[k+1][j],dp[i][j]);//标准状态转移方程,为了将最长的长度一个个的代下来以便于下次的加和。就是说dp[i-1][j]的值决定了dp[i][j]的值。
                }
            }
        }
        cout<<dp[0][s.size()-1]<<endl;

(3)。n个字符组成长度为m的字符串,给出增删字符的花费,可在字符串任意位置增删字符,求把字符串修改成回文串的最小花费。

规定dp[i][j]为将ij区间改成回文串的最小花费,[i+1,j]前面的字符加一个使状态方程的i后移,或是前面的i减去,结果仍然会是保存的字符后移了一位,[i][j-1]类似。

当a[i]==a[j]时,dp[i][j]=dp[i-1][j-1];

完整代码:

const int MAXN = 2005;
char a[MAXN], ch;
int dp[MAXN][MAXN];
int add[30],sub[30];
int main()
{
    int n, m;
    scanf("%d %d", &n, &m);
    scanf("%s", a+1);
    for (int i = 1; i <= n; i++)
    {
        scanf(" %c", &ch);
        scanf("%d %d", &add[ch-'a'], &sub[ch-'a']);//增加或者减少这些ASCII码值需要的花费
    }
    memset(dp, 0x3f, sizeof(dp));//因为要求最小值
    for (int i = 1; i <= m; i++) 
        dp[i][i] = 0;
    for (int len = 2; len <= m; len++)
        for(int i = 1, j = len; j <= m; i++, j++)
        {
            dp[i][j] = min(dp[i][j], min(add[a[i]-'a'],sub[a[i]-'a']) + dp[i+1][j]);//前面字符的删减
            dp[i][j] = min(dp[i][j], dp[i][j-1] + min(add[a[j]-'a'],sub[a[j]-'a']));//后面字符的删减
            if (a[i] == a[j])//字符相等时直接赋值 
            {
                if (len==2) //才开始的时候要变成从零开始,否则是个极大值。
                    dp[i][j] = 0;
                else
                   dp[i][j] = min(dp[i][j], dp[i+1][j-1]);
            }
        }
    printf("%d\n", dp[1][m]);

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值