这一类的问题总结起来就是可以消除掉一段区间,之后原来的区间的两边会合起来,产生一种新的结果。
解决的一般办法是假设一段已经处理过了,并记录到后面/前面的状态里面去。
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 1If 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 2The 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
Solution
经典例题。
先把可以合并的都合并起来,然后对合并完的每一个元素分别处理,分情况讨论。
设
f[i][j][k]
表示合并
[i,j]
这个区间,j与后面k个颜色相同的一起考虑的最大得分。
首先枚举长度,枚举区间,对于一个区间来说,考虑右端点的合并情况:
- 右端点自己与后面的k个合并, [i,j−1] 合并起来。
- 右端点与
[i,j−1]
区间内的一个与右端点颜色相同
l
一起合并,
f[i][l][k+len[j]]+f[l+1][j−1][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
个串的
对于一个区间的转移的情况:
- 不要这个区间,全部剩下来,那这个区间之前挂在前面的也就剩下了;
- 把 i 这个字符合并到前面挂起来,如果前面那一段是空的,那就新开一个;
- 枚举
[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;
}