【学习】 区间消除类dp

这一类的问题总结起来就是可以消除掉一段区间,之后原来的区间的两边会合起来,产生一种新的结果。
解决的一般办法是假设一段已经处理过了,并记录到后面/前面的状态里面去。

poj 1390 Blocks

Description

Some of you may have played a game called ‘Blocks’. There are n blocks in a row, each box has a color. Here is an example: Gold, Silver, Silver, Silver, Silver, Bronze, Bronze, Bronze, Gold.
The corresponding picture will be as shown below:

这里写图片描述
Figure 1

If some adjacent boxes are all of the same color, and both the box to its left(if it exists) and its right(if it exists) are of some other color, we call it a ‘box segment’. There are 4 box segments. That is: gold, silver, bronze, gold. There are 1, 4, 3, 1 box(es) in the segments respectively.

Every time, you can click a box, then the whole segment containing that box DISAPPEARS. If that segment is composed of k boxes, you will get k*k points. for example, if you click on a silver box, the silver segment disappears, you got 4*4=16 points.

Now let’s look at the picture below:

这里写图片描述
Figure 2

The first one is OPTIMAL.

Find the highest score you can get, given an initial state of this game.

Input

The first line contains the number of tests t(1<=t<=15). Each case contains two lines. The first line contains an integer n(1<=n<=200), the number of boxes. The second line contains n integers, representing the colors of each box. The integers are in the range 1~n.

Output

For each test case, print the case number and the highest possible score.

Sample Input

2
9
1 2 2 2 2 3 3 3 1
1
1

Sample Output

Case 1: 29
Case 2: 1

Source

Liu Rujia@POJ

Solution

经典例题。
先把可以合并的都合并起来,然后对合并完的每一个元素分别处理,分情况讨论。
f[i][j][k] 表示合并 [i,j] 这个区间,j与后面k个颜色相同的一起考虑的最大得分。
首先枚举长度,枚举区间,对于一个区间来说,考虑右端点的合并情况:

  1. 右端点自己与后面的k个合并, [i,j1] 合并起来。
  2. 右端点与 [i,j1] 区间内的一个与右端点颜色相同 l 一起合并,f[i][l][k+len[j]]+f[l+1][j1][0]

然后就可以合并了。

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
const int maxn = 201;
int T, n, m, v[maxn], f[maxn][maxn][maxn];
int pre[maxn], po[maxn], len[maxn], cnt, now;
int sqr(int x){return x*x;}
int main(){
    scanf("%d", &T);
    for(int TT = 1; TT <= T; TT ++){
        scanf("%d", &n);
        memset(f, 0, sizeof f), memset(pre, 0, sizeof pre);
        memset(po, 0, sizeof po), memset(len, 0, sizeof len);
        cnt = 0, now = 0;
        for(int i = 1; i <= n; i ++) scanf("%d", &v[i]);
        v[++cnt] = v[1], len[cnt] ++;
        for(int i = 2; i <= n; i ++){
            if(v[cnt] == v[i]) len[cnt] ++;
            else v[++ cnt] = v[i], len[cnt] = 1;
        } m = n; n = cnt;
        for(int i = 1; i <= n; i ++) pre[i] = po[v[i]], po[v[i]] = i;
        for(int i = 1; i <= n; i ++)
            for(int j = 0; j <= n; j ++)
                f[i][i][j] = sqr(len[i]+j);
        for(int i = 2; i <= n; i ++)
            for(int j = 1; j+i-1 <= n; j ++)
                for(int k = 0; k <= m; k ++){
                    int ii = j, jj = i+j-1;
                    f[ii][jj][k] = f[ii][jj-1][0] + sqr(len[jj]+k);
                    for(int l = pre[jj]; l >= j; l = pre[l])
                        f[ii][jj][k] = max(f[ii][jj][k], f[ii][l][k+len[jj]] + f[l+1][jj-1][0]);
                }
        printf("Case %d: %d\n", TT, f[1][n][0]);
    }
    return 0;
}

BZOJ 2121: 字符串游戏

Description

BX正在进行一个字符串游戏,他手上有一个字符串L,以及其他一些字符串的集合S,然后他可以进行以下操作:对于一个在集合S中的字符串p,如果p在L中出现,BX就可以选择是否将其删除,如果删除,则将删除后L分裂成的左右两部分合并。举个例子, L=abcdefg,S={de} ,如果BX选择将 de 从L中删去,则删后的 L=abcfg 。现在BX可以进行任意多次操作(删的次数,顺序都随意),他想知道最后L串的最短长度是多少。

Input

输入的第一行包含一个字符串,表示L。第二行包含一个数字n,表示集合S中元素个数。以下n行,每行一个字符串,表示S中的一个元素。输入字符串都只包含小写字母。

Output

输出一个整数,表示L的最短长度。

Sample Input

aaabccd
3
ac
abc
aaa

Sample Output

2

HINT

aaabccd
aacd
ad
对于100%数据,满足 |L|<151,|S|<31 , S 中的每个元素|p|<21

Solution

f[i][j][a][b] 表示现在要消除的是 [i,j] ,这个区间的前面还挂着已经匹配的第 a 个串的b个字符。
对于一个区间的转移的情况:

  1. 不要这个区间,全部剩下来,那这个区间之前挂在前面的也就剩下了;
  2. i 这个字符合并到前面挂起来,如果前面那一段是空的,那就新开一个;
  3. 枚举[i,k]来消除掉,然后把原来这段区间挂着的元素给 [k+1,j] 这段区间。
#include <bits/stdc++.h>

using namespace std;

int n, m, f[155][155][35][25];
char c[155], s[35][25];


inline void u(int &a, int b){if(a > b) a = b;}

int dp(int i, int j, int a, int b){
    if(i > j) return b;
    int &cur = f[i][j][a][b];
    if(~cur) return cur;
    cur = j-i+1+b;
    u(cur, dp(i+1, j, 0, 0)+b+1);
    if(a){
        if(c[i] == s[a][b+1]){
            if(s[a][0] == b+1) u(cur, dp(i+1, j, 0, 0));
            else u(cur, dp(i+1, j, a, b+1));
        }
    }else{
        for(int k = 1; k <= m; k ++){
            if(c[i] == s[k][1]){
                if(s[k][0] == 1) u(cur, dp(i+1, j, 0, 0));
                else u(cur, dp(i+1, j, k, 1));
            }
        }
    }
    for(int k = i; k < j; k ++)
        if(dp(i, k, 0, 0) == 0)
            u(cur, dp(k+1, j, a, b));
    return cur;
}

int main(){
    memset(f, -1, sizeof(f));
    scanf("%s%d", c+1, &m);
    n = strlen(c+1);
    for(int i = 1; i <= m; i ++){
        scanf("%s", s[i]+1);
        s[i][0] = strlen(s[i]+1);
    } printf("%d\n", dp(1, n, 0, 0));
    return 0;
} 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值