[OI]字符串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;
}

P1279 字串距离

题目描述

设有字符串X,我们称在X的头尾及中间插入任意多个空格后构成的新字符串为X的扩展串,如字符串X为”abcbcd”,则字符串“abcb□cd”,“□a□bcbcd□”和“abcb□cd□”都是X的扩展串,这里“□”代表空格字符。

如果A1是字符串A的扩展串,B1是字符串B的扩展串,A1与B1具有相同的长度,那么我扪定义字符串A1与B1的距离为相应位置上的字符的距离总和,而两个非空格字符的距离定义为它们的ASCII码的差的绝对值,而空格字符与其他任意字符之间的距离为已知的定值K,空格字符与空格字符的距离为0。在字符串A、B的所有扩展串中,必定存在两个等长的扩展串A1、B1,使得A1与B1之间的距离达到最小,我们将这一距离定义为字符串A、B的距离。

请你写一个程序,求出字符串A、B的距离。

输入输出格式

输入格式:

输入文件第一行为字符串A,第二行为字符串B。A、B均由小写字母组成且长度均不超过2000。第三行为一个整数K(1≤K≤100),表示空格与其他字符的距离。

输出格式:

输出文件仅一行包含一个整数,表示所求得字符串A、B的距离。

输入输出样例

输入样例#1

cmc
snmn
2

输出样例#1:

10

\(dp[i][j]\)表示第一个串的前i个字符和第二个串的前j个字符的最优值,两个空格对应显然没有意义,那么有3种转移:\(dp[i-1][j]+K\)\(dp[i][j-1]+K\)\(dp[i-1][j-1]+abs(S1[i]-S2[j])\),分别表示S1[i]与空格匹配,S2[j]与空格匹配,S1[i]与S2[j]匹配。

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

const int MAXN = 2010;
int K;
char S1[MAXN], S2[MAXN];
int dp[MAXN][MAXN];

int clac(int i, int j) {
	return abs((int) (S1[i] - 'a') - (int) (S2[j] - 'a'));
}

int main( ) {
	scanf("%s%s%d", S1 + 1, S2 + 1, &K);
	int len1 = strlen(S1 + 1), len2 = strlen(S2 + 1); 
	memset(dp, 63, sizeof(dp));
	dp[0][0] = 0;
	for (int i = 0; i <= len1; ++ i)
		for (int j = 0; j <= len2; ++ j) {
			if (i) dp[i][j] = min(dp[i][j], dp[i - 1][j] + K);
			if (j) dp[i][j] = min(dp[i][j], dp[i][j - 1] + K);
			if (i && j)  
				dp[i][j] = min(dp[i][j], dp[i - 1][j - 1] + clac(i, j));
		}
	printf("%d\n", dp[len1][len2]);
	return 0;
}

caioj 1061: [视频]背包7(匹配性填满型 完全 背包)

时间限制: 1 Sec 内存限制: 128 MB

【问题描述】
判断句子是否可以被划分成若干单词,这些单词只可以 “one”、“puton”、“out”、“output”、“in”和“input”。
输入n个字符串,长度不超过1000000,表示一句句子。
如果可能是那两个人的对话,则输出“YES”;否则,输出“NO”。
【输入文件】
第一行一个整数n,表示一共有n句句子。
此后每行一个字符串,表示一句句子。
【输出文件】
n行,每行一个“YES”或“NO”,表示你的判断结果。

样例输入输出

样例输入

6
puton
inonputin
oneputonininputoutoutput
oneininputwooutoutput
outpu
utput

样例输出

YES
NO
YES
NO
NO
NO

如果不知道这是一道背包题的话,可能没几个人会往背包的方面想。我们不妨把题目中给定的6个单词看做六个数量无限的物品,现在他们要装到一个背包中,比如要装一个input,能装入背包的条件是当前装了一些的背包中,再往后需要的字母依次是i,n,p,u,t。最后成功的条件是背包被装满,即\(dp[串长]\)有值。\(dp[i]\)表示前i个字符是否能完成匹配。如上所述,则dp[i]能由dp[i - len[i]] 推出,当且仅当,子串c[j - len[i] ~ j] 为给定的单词。

单纯这样做还会TLE。可以简单优化一下,具体参照代码。

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

const int MAXM = 1000010;
const int MAXN = 1010;
int n;
string c[] = {"", "one", "puton", "out", "output", "in", "input"};
int len[] = {0, 3, 5, 3, 6, 2, 5};
int dp[MAXM];

int main( ) {
	scanf("%d", &n);
	string s; int l;
	for (int i = 1; i <= n; ++ i) {
		memset(dp, 0, sizeof(dp));
		cin >> s; 
		l = s.size( );
		dp[0] = 1;
		for (int j = 1; j <= l; ++ j)
		if(s[j - 1] == 'e' || s[j - 1] == 'n' || s[j - 1] == 't') //优化
			for (int i = 1; i <= 6; ++ i) 
				if (j - len[i] >= 0) 
				if(s[j - len[i]] == 'o' || s[j - len[i]] == 'p' || s[j - len[i]] == 'i') //优化
					if (s.substr(j - len[i], len[i]) == c[i])
						dp[j] = dp[j] | dp[j - len[i]];
		if (dp[l]) printf("YES\n");
		else printf("NO\n");
	}
}

P1136 迎接仪式

题目描述

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

为了简单描述这个不和谐的队列,我们用“j”替代“教”,“z”替代“主”。而一个“j”与“z”组成的序列则可以描述当前的队列。为了让教主看得尽量舒服,你必须调整队列,使得“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; 
}
posted @ 2018-12-22 20:41  wakelin  阅读(842)  评论(1编辑  收藏  举报