[DP总结]字符串DP

咕咕咕
很久没写博客了,停课后总结一下DP吧。

声明:本文中题目均来自@DDOSVOID神仙的个人总结题目,禁止转载。侵删。

字符串DP

顾名又思义,是在字符串上进行的DP操作。因为字符串本身可以看作是一个序列,所以有些时候字符串DP可以用区间DP来解决。

P2246 SAC#1 - Hello World(升级版)

题目描述

在讲义的某一面,他看见了一篇文章。这篇文章由英文字母(大小写均有)、数字、和空白字符(制表/空格/回车)构成。

pipapi想起了他最近刚刚学会写的Hello World程序。他非常好奇,这篇文章中,“HelloWorld”作为子序列到底出现过多少次呢?

由于papapi是个智障,大小写对于他而言毫无区别;因此,“hEllOWorLD”这样的子序列也是可以接受的。O和W之间的空格是也是可以少的;也就是说,“HelloWorld”是可以的。根据标程的意思,就是没有空格,不用考虑空格的情况。

两个子序列相同当且仅当它们每一个字符所在的位置都相同。

由于答案可能很大,请输出结果对1000000007(10^9+7)的余数。

输入输出格式

输入格式:

输入包含若干行。这些行的内容共同构成一篇文章。

文章以EOF(文件结尾)结束。

输出格式:

输出仅包含一个整数,表示这篇文章中“Hello World”出现的次数。 d

输入输出样例

输入样例#1:

HhEeLlLlOoWwOoRrLlDd

输出样例#1:

1536

输入样例#2:

Gou Li Guo Jia Sheng Si Yi
Qi Yin Huo Fu Bi Qu Zhi
River can feed people
Also can race boats
Hall Ellen Ok Words locked 

输出样例#2:

273

说明

记n为输入的文章的长度(字符数)。

对于20%的数据,n <= 20。

对于50%的数据,n <= 500。

对于所有的数据,15 <= n <= 500000。

入门题。设\(dp[i][j]\)表示文本串前i个字符匹配helloworld模板的前j个字符的匹配数。显然当\(a[i]=b[j]\)时有\(dp[i][j]=dp[i-1][j-1] + dp[i-1][j]\),其他情况\(dp[i][j]=dp[i-1][j]\)。前面一维直接滚动优化掉。

#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <iostream>
#include <algorithm>
using namespace std;

typedef long long LL;
const int MOD = 1e9 + 7;
char ch1[233] = "#helloworld";
char ch2[233] = "#HELLOWORLD";
int f[233];

int main() {
    char x; f[0] = 1;
    while ((x = getchar())!=EOF) 
        for (int i = 10; i >= 1; -- i) 
            if (x == ch1[i] || x == ch2[i]) 
                f[i] = (f[i-1] + f[i]) % MOD;
    cout << f[10] << endl;
    return 0;
}

P2890 [USACO07OPEN]便宜的回文Cheapest Palindrome

题目描述

Keeping track of all the cows can be a tricky task so Farmer John has installed a system to automate it. He has installed on each cow an electronic ID tag that the system will read as the cows pass by a scanner. Each ID tag's contents are currently a single string with length M (1 ≤ M ≤ 2,000) characters drawn from an alphabet of N (1 ≤ N ≤ 26) different symbols (namely, the lower-case roman alphabet).

Cows, being the mischievous creatures they are, sometimes try to spoof the system by walking backwards. While a cow whose ID is "abcba" would read the same no matter which direction the she walks, a cow with the ID "abcb" can potentially register as two different IDs ("abcb" and "bcba").

FJ would like to change the cows's ID tags so they read the same no matter which direction the cow walks by. For example, "abcb" can be changed by adding "a" at the end to form "abcba" so that the ID is palindromic (reads the same forwards and backwards). Some other ways to change the ID to be palindromic are include adding the three letters "bcb" to the begining to yield the ID "bcbabcb" or removing the letter "a" to yield the ID "bcb". One can add or remove characters at any location in the string yielding a string longer or shorter than the original string.

Unfortunately as the ID tags are electronic, each character insertion or deletion has a cost (0 ≤ cost ≤ 10,000) which varies depending on exactly which character value to be added or deleted. Given the content of a cow's ID tag and the cost of inserting or deleting each of the alphabet's characters, find the minimum cost to change the ID tag so it satisfies FJ's requirements. An empty ID tag is considered to satisfy the requirements of reading the same forward and backward. Only letters with associated costs can be added to a string.

字串S长M,由N个小写字母构成。欲通过增删字母将其变为回文串,增删特定字母花费不同,求最小花费。

输入输出格式

输入格式:

Line 1: Two space-separated integers: N and M

Line 2: This line contains exactly M characters which constitute the initial ID string

Lines 3..N+2: Each line contains three space-separated entities: a character of the input alphabet and two integers which are respectively the cost of adding and deleting that character.

输出格式:

Line 1: A single line with a single integer that is the minimum cost to change the given name tag.

输入输出样例

输入样例#1:

3 4
abcb
a 1000 1100
b 350 700
c 200 800

输出样例#1:

900

说明

If we insert an "a" on the end to get "abcba", the cost would be 1000. If we delete the "a" on the beginning to get "bcb", the cost would be 1100. If we insert "bcb" at the begining of the string, the cost would be 350 + 200 + 350 = 900, which is the minimum.

比较经典的字符串DP了。设\(dp[i][j]\)表示区间[i,j]变成回文串的最小花费,需要用到区间DP的思想。考虑如何用一个小区间更新一个大区间。如果大区间是\(dp[i][j]\),若s[i]=s[j],那么\(dp[i][j]=dp[i+1][j-1]\),即当前大区间可以由去掉其两端的小区间更新而来而不用花费。不等的时候,\(dp[i][j]=min(dp[i+1][j]+min(add[s[i]],del[s[i]]),dp[i][j-1]+min(add[s[j],del[s[j]]])\)

#include<iostream>
#include<algorithm>
using namespace std;

const int M = 2005, N = 256;
int n, m;
char c, s[M];
int del[N], add[N], f[M][M];

int main() {
    scanf("%d%d%s", &n, &m, (s+1));
    for (int i = 1; i <= n; ++ i) {
        cin >> c; 
        cin >> add[c] >> del[c];
    } 
    for (int L = 2; L <= m; ++ L) 
        for (int i = 1; i + L - 1 <= m; ++ i) {
            int j = i + L - 1;
            if (s[i] == s[j]) f[i][j] = f[i + 1][j - 1];
            else f[i][j] = min(f[i+1][j] + min(add[s[i]], del[s[i]]),
                               f[i][j-1] + min(add[s[j]], del[s[j]]));
        }
    cout<<f[1][m]<<endl;
    return 0;
}

P1136 迎接仪式

题目描述

LHX教主要来X市指导OI学习工作了。为了迎接教主,在一条道路旁,一群Orz教主er穿着文化衫站在道路两旁迎接教主,每件文化衫上都印着大字。一旁的Orzer依次摆出“欢迎欢迎欢迎欢迎……”的大字,但是领队突然发现,另一旁穿着“教”和“主”字文化衫的Orzer却不太和谐。

为了简单描述这个不和谐的队列,我们用“j”替代“教”,“z”替代“主”。而一个“jjj”与“zzz”组成的序列则可以描述当前的队列。为了让教主看得尽量舒服,你必须调整队列,使得“jz”子串尽量多。每次调整你可以交换任意位置上的两个人,也就是序列中任意位置上的两个字母。而因为教主马上就来了,时间仅够最多作K次调整(当然可以调整不满K次),所以这个问题交给了你。

输入输出格式

输入格式:

第一行包含2个正整数N与K,表示了序列长度与最多交换次数。

第二行包含了一个长度为N的字符串,字符串仅由字母“j”与字母“z”组成,描述了这个序列。

输出格式:

一个非负整数,为调整最多K次后最后最多能出现多少个“jz”子串。

输入输出样例

输入样例#1:

5 2 
zzzjj

输出样例#1:

2

说明

【样例说明】

第1次交换位置1上的z和位置4上的j,变为jzzzj;

第2次交换位置4上的z和位置5上的j,变为jzzjz。

最后的串有2个“jz”子串。

【数据规模与约定】

对于10%的数据,有N≤10;

对于30%的数据,有K≤10;

对于40%的数据,有N≤50;

对于100%的数据,有N≤500,K≤100。

一开始不知道怎么做。考虑\(dp[i][j][k]\)表示考虑前i个字符,有j个j变成了z,k个z变成了j

然后呢?

然后我就不知道了

首先显然两个一样的字符不会被修改。

相邻两字符有四种情况可以转移:zj jz jj zz

\(s[i]='j' ~\&\&~ s[i-1]='z'\)\(dp[i][j][k]=max(dp[i][j][k],dp[i-2][j][k]+1);\)

\(s[i]='z' ~\&\&~ s[i-1]='j'\)\(dp[i][j][k]=max(dp[i][j][k],dp[i-2][j-1][k-1]+1);\)

上面两种情况都比较显然。

那么另外两种情况呢?

\(s[i]='j' ~\&\&~ s[i-1]='j'\)\(dp[i][j][k]=max(dp[i][j][k],dp[i-2][j-1][k]+1);\)

\(s[i]='z' ~\&\&~ s[i-1]='z'\)\(dp[i][j][k]=max(dp[i][j][k],dp[i-2][j][k-1]+1);\)

感性理解一下就是,我把不合法的变成合法的,可以看做当前不合法的与前面或后面某个数交换了一下,使之合法,但具体是与哪个数交换的,我们不需要去知道。因为当交换次数\(j=k\)时,显然是存在至少一种合法的操作顺序,令原字符串可以通过j=k次交换变成合法。当交换次数\(j \not= k\)时,显然不存在这种交换方式,那我们就不必去管他。

最后对所有的\(dp[N][i][i]\)取max。

比较巧妙。

顺便,注意边界处理。

#include <algorithm>
#include <cctype>
#include <cmath>
#include <cstring>
#include <cstdlib>
#include <cstdio>
#include <ctime>
#include <iostream>
#include <map>
#include <queue>
#include <vector>
using namespace std;

const int INF = 0x3f3f3f3f;
const int MAXN = 510;
const int MAXK = 110;
int N, K;
//dp[i][j][k]表示考虑前i个字符,有j个‘j’变成了z,k个‘z’变成了j
int l[MAXN], dp[MAXN][MAXK][MAXK];
char tmp[MAXN];

template <typename _Tp>
inline void read(_Tp &x) {
    char ch = getchar( ); bool f = 0; x = 0;
    while (!isdigit(ch)) { if (ch == '-') f = 1; ch = getchar( ); }
    while (isdigit(ch)) x = x * 10 + ch - '0', ch = getchar( );
    x = f ? -x : x;
}

int main( ) {
    memset(dp, ~63, sizeof(dp));
    read(N), read(K);
    scanf("%s", tmp + 1);
    for (int i = 1; i <= N; ++ i) 
        if (tmp[i] == 'z') l[i] = 1;
        else l[i] = 0;
    //for (int i = 1; i <= N; ++ i) printf("%d\n", l[i]);
    dp[0][0][0] = dp[1][0][0] = dp[1][l[1]][l[1]] = 0;
    for (int i = 2; i <= N; ++ i) 
        for (int j = 0; j <= K; ++ j)
            for (int k = 0; k <= K; ++ k) {
                dp[i][j][k] = dp[i - 1][j][k];
                if (!l[i - 1] && l[i]) 
                    dp[i][j][k] = max(dp[i][j][k], dp[i - 2][j][k] + 1);
                if (k && l[i] && l[i - 1])
                    dp[i][j][k] = max(dp[i][j][k], dp[i - 2][j][k - 1] + 1);
                if (j && !l[i] && !l[i - 1])
                    dp[i][j][k] = max(dp[i][j][k], dp[i - 2][j - 1][k] + 1);
                if (j && k && !l[i] && l[i - 1]) 
                    dp[i][j][k] = max(dp[i][j][k], dp[i - 2][j - 1][k - 1] + 1);
            }
    int ans = 0;
    for (int i = 0; i <= K; ++ i) ans = max(ans, dp[N][i][i]);
    printf("%d\n", ans);
    return 0; 
}

猜你喜欢

转载自www.cnblogs.com/hkttg/p/9832263.html