区间dp 入门模板题

poj2955

题意:
求最长的”正则括号“的长度,正则括号定义如下:
1.空串是正则括号
2.如果s是正则括号,则(s)和[s]也是
3.如果a和b都是正则括号,则ab也是
4.第四条没看懂,(正则序列可以是原来序列的子序列(我自己胡乱说的))
分析:
区间dp,区间长度从1枚举到最长.(dp[i][j]表示i~j这个串中正则序列最长的长度)
如果区间端点处两个字符匹配,那么dp[i][j] = dp[i+1][j-1] + 2,然后再更新这个区间的值,枚举这个区间的间断点k,dp[i][j]的值与左半区间值加右半区间的值取最大值。
AC代码:

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>

using namespace std;

string s;
int dp[105][105];
int len;

bool match(int i, int j)
{
    if(s[i] == '(' && s[j] == ')') return true;
    if(s[i] == '[' && s[j] == ']') return true;
    return false;
}

int main()
{
    while(cin >> s && s[0] != 'e')
    {
        memset(dp, 0, sizeof(dp));
        len = s.size();
        for(int d = 1; d <= len; ++d)
        {
            for(int i = 0; i < len - 1 && i + d - 1 < len; ++i)
            {
                int j = i + d - 1;
                if(match(i, 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][len - 1] << '\n';
    }
    return 0;
}

石子合并系列

三类

(1)有N堆石子,现要将石子有序的合并成一堆,规定如下:每次只能移动任意的2堆石子合并,合并花费为新合成的一堆石子的数量。求将这N堆石子合并成
这种甚至可以不用dp,直接用堆贪心即可。

(2)有N堆石子,现要将石子有序的合并成一堆,规定如下:每次只能移动相邻的2堆石子合并,合并花费为新合成的一堆石子的数量。求将这N堆石子合并成一堆的总花费最小(或最大)。

(3)把问题(2)中排成一堆改成排成一个环

石子合并(直线版)

分析:
dp[i][j]表示把第i~j堆石子合并的最小花费
sum数组是前缀和
要求的是dp[i][j]的最小值,我们可以枚举分界点k,即前n-2次合并把石子变成了1~k为一堆, k +1 ~ n为一堆,那么自然dp[i][j] = min(dp[i][j], dp[i][k] + dp[k + 1][j] + sum[j] - sum[i - 1])
然后需要注意的就是求dp[i][j]的时候要保证dp[i][k]和dp[k+1][j]已经求出来了,那么就需要注意一下循环的顺序。
AC代码:

#include <bits/stdc++.h>

using namespace std;

int n;
int ar[1005], dp[1005][1005];
int sum[1005];

int main()
{
    scanf("%d", &n);
    for(int i = 1; i <= n; ++i)
    {
        scanf("%d", &ar[i]);
        sum[i] = sum[i - 1] + ar[i];
    }
    memset(dp, 0x3f, sizeof(dp));
    for(int i = 1; i <= n; ++i) dp[i][i] = 0;
    //枚举顺序是区间后端点从小到大,区间长度从小到大
    for(int j = 1; j <= n; ++j)
    {
        for(int i = j - 1; i >= 1; --i)
        {
            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]);
            }
        }
    }
    printf("%d\n", dp[1][n]);
    return 0;
}

石子合并(环)

分析:
大体思路跟上面那个题一样。只是需要对环处理,处理方法以下面为例
对于样例

4
4 5 9 4

变成4 5 9 4 4 5 9 4(变成原来的两倍),然后答案从dp[1][n],dp[2][n + 1], dp[3][n + 2],…, dp[n][2 * n - 1]中取,实质是枚举合并n-2次后两堆石子的状态,即在哪两个点处断成两个链
AC代码:

#include <bits/stdc++.h>

using namespace std;

int n;
int ar[1005];
int f1[1005][1005], f2[1005][1005];
int sum[1005];
int mi = 0x3f3f3f3f, mx = -1;

int main()
{
    scanf("%d", &n);
    for(int i = 1; i <= n; ++i)
    {
        scanf("%d", &ar[i]);
        sum[i] = sum[i - 1] + ar[i];
    }
    for(int i = n + 1; i <= 2 * n; ++i)
    {
        ar[i] = ar[i - n];
        sum[i] = sum[i - 1] + ar[i];
    }
    memset(f1, 0x3f, sizeof(f1));
    for(int i = 1; i <= 2 * n; ++i) f1[i][i] = 0;
    for(int j = 1; j <= 2 * n; ++j)
    {
        for(int i = j - 1; i >= 1; --i)
        {
            for(int k = i; k < j; ++k)
            {
                f1[i][j] = min(f1[i][j], f1[i][k] + f1[k + 1][j] + sum[j] - sum[i - 1]);
                f2[i][j] = max(f2[i][j], f2[i][k] + f2[k + 1][j] + sum[j] - sum[i - 1]);
            }
        }
    }
    for(int i = 1; i <= n; ++i)
    {
        mx = max(mx, f2[i][i + n - 1]);
        mi = min(mi, f1[i][i + n - 1]);
    }
    cout << mi << '\n' << mx << '\n';
    return 0;
}

POJ2287 田忌赛马

题意:
给你n匹田忌的马的速度,n批齐王马的速度,傻逼齐王只会按照马的速度排序,显出最快的马。一共会比n次,输的一方需要给赢的一方200元,输出田忌最多挣多少钱。
分析:
首先很容易想到贪心,先对他俩的马排序,然后遍历齐王的每一匹马,如果能打过就打,打不过就那最笨的马跟他打。
但是这样存在一个问题,就是如果平局(两人最牛逼的一样厉害)这是平局会消耗田忌一匹最牛逼的马,然后不扣钱,选一个最笨的马去打会保留这匹牛逼的马,但是会扣200块,这两种做法在不同的情况下效果不同。
对于下面的例子
• 例子1:
• 田忌 :1 2 3
• 齐王 :1 2 3
(应该先输)
• 例子2:
• 田忌:2 3
• 齐王:1 3
(应该打平局)

再仔细想想会发现,田忌每次排的马不是最牛逼的就是最拉跨的,所以我们可以动态规划。
用f[i][j]表示田忌区间[i~j]的马比完之后,田忌挣的钱,则状态转移方程是

f[i][j] = max(f[i+1][j] + cost(i,k), f[i][j-1] + cost(j,k));
//k表示齐王当前出的马,cost(i,k)是田忌第i匹马与齐王第k匹马相比的结果。

要搞明白田忌的每个区间对应的是齐王的那一匹马区间[i~j]说明已经有n - (j - i + 1)只马比完了,那么对应的就是第n - (j - i + 1) + 1只马

然后是边界情况f[i][i],即田忌只剩下第i匹马了,齐王只剩下最笨的那匹马了,它的值应该等于cost(i,n)

这个题还有一个需要理解的是他们比赛是从n匹马比到1匹马,而我们推的时候是先得到齐王只有一匹马(最笨的那一匹)的时候,田忌有一匹马(不知道是那匹)的时候他们挣钱的情况,然后由这个边界条件推回去,正好跟比赛的顺序相反。
AC代码:

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>

using namespace std;

int n;
int ar[1005], br[1005];
int f[1005][1005];

bool cmp(int a, int b)
{
    return a > b;
}

int cost(int i, int j)
{
    if(ar[i] > br[j]) return 200;
    else if(ar[i] == br[j]) return 0;
    else return -200;
}

int main()
{
    while(scanf("%d", &n) != EOF && n)
    {
        memset(f, 0, sizeof(f));
        for(int i = 1; i <= n; ++i) scanf("%d", &ar[i]);
        for(int i = 1; i <= n; ++i) scanf("%d", &br[i]);
        sort(ar + 1, ar + 1 + n, cmp);
        sort(br + 1, br + 1 + n, cmp);
        for(int i = 1; i <= n; ++i) f[i][i] = cost(i, n);
        for(int j = 1; j <= n; ++j)
        {
            for(int i = j - 1; i >= 1; --i)
            {
                int k = n - (j - i + 1) + 1;
                f[i][j] = max(f[i + 1][j] + cost(i, k), f[i][j - 1] + cost(j, k));
            }
        }
        printf("%d\n", f[1][n]);
    }
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值