最长公共子序列(LCS)问题 hdu 1243 反恐训练营

缘起

最长公共子序列(LongestCommon Subsequence, LCS)是DP的经典问题. 遂找一道板题测一下.

hdu 1243 反恐训练营

分析

题意是中文的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
当今国际反恐形势很严峻,特别是美国“9.11事件”以后,国际恐怖势力更是有恃无恐,制造了多起骇人听闻的恐怖事
件。基于此,各国都十分担心恐怖势力会对本国社会造成的不稳定,于是纷纷在本国的军队、警察队伍中开展了反恐
训练。作为反恐立场坚定的大国,中国也十分重视在人民解放军、武装警察部队、人民警察队伍中反恐训练,还专门
成立了反恐特警队。

炜炜是反恐特警队的一名新队员,现在正在接受培训。这几天刚好是射击训练第二阶段——实弹应变训练的日子,此前
的第一阶段里,炜炜经过努力,已经将自己训练成为一个百发百中的神抢手了!这次,他将背着国产最新型12.7mm重
型狙击枪进行训练比赛。

这次训练比赛的规则是这样的:

1、每个队员从出发点开始,沿着一条唯一的笔直道路跑直到终点,途中不允许往回跑,否则将被取消比赛资格。
2、出发前,每个队员的枪膛内都被装了顺序一样的、用小写英文字母标明类型的子弹序列,每位队员被告知这一序列
的信息;同时,每位队员也被告知恐怖分子即将出现的序列和类型(同样用小写英文字母标明类型)。
3、在跑动的过程中,若发现“恐怖分子”,特警队员可以选择用枪击毙他,来得到写在“恐怖分子”胸前的得分,但是
前提是他使用的子弹类型必须和“恐怖分子”类型相同,否则,即使击毙了“恐怖分子”,也得不到分数;当然选择不击
毙他也是可以的,这样他不会从那个“恐怖分子”身上得到分数。
4、允许特警队员放空枪,这样可以消耗掉型号不对的子弹而不至于杀死“恐怖分子”(当然每个特警队员都不会愚蠢
到不装消音装置就放空枪,以至于吓跑“恐怖分子”),等待枪口出现正确型号的子弹击毙他得分。

这里,我们假定:
1、对于每个队员,途中出现恐怖分子的地点、时间、类型也是完全一样的。
2、每颗子弹都是质量合格的,都可以发挥杀伤效力
3、由于队员各个都是神枪手,一旦他选择了正确的子弹,向目标射击,目标100%被爆头
4、每个队员的记忆力超强,能记住所有子弹序列信息和恐怖分子序列信息。
5、每个队员体力足够好,能跑完全程,并做他想要做的
6、“恐怖分子”是不动的,小范围内不存在多于一个的恐怖分子;

炜炜需要你的帮助,告诉他如何做,才能得到最高的分数。现在如果告诉你出发时枪膛内子弹的序号和型号、恐怖分
子出现的序号和类型,你能告诉炜炜他最多能得到多少分数吗?

【输入】
输入数据的第一行有一个整数N表示子弹和恐怖分子的类型数。随后的一行是各种恐怖分子类型的一行字母,两个字母之间没有任何字符。接下来的一行是击毙上一行对应位置恐怖分子类型的得分数,每个分数之间恰有一个空格。第三第四行分别表示开始时枪膛内子弹的序列(左边的先打出)和恐怖分子出现的序列(左边的先出现),字母之间都没有任何字符。
每个测试数据之间没有空格和空行。你的程序必须通过全部测试数据,才能被判为AC。

【输出】
对于每一个测试数据,输出炜炜最多能得到的分数。

【样例输入】
3
abc
1 1 1
abc
ccc
3
abc
1 1 1
ccc
aba

【样例输出】
1
0

【限制】
Time limit 1000 ms
Memory limit 32768 kB

很显然,算法就是经典的LCS的算法,即dp. 因为其实这就是在求解2个序列之间的最长公共子序列——只不过这个最长是带权的(分数值). 依旧是使用dp解决.

下面分析问题的最优子结构

令s[0,…,n-1] 和t[0,…,m-1] 是两个子序列. dp[i][j] 是 s[0,…,i] 和 t[0,..,j] 的最优解. 则我们考虑 dp[i][j]的递推关系式

1
2
3
4
5
6
欲求 dp[i][j] 0<=i<n, 0<=j<m
情形1: s[i]==t[j], 则可以使用反证法证明, s[i]和t[j]一定是LCS的最后一个元素. 即
dp[i][j]=dp[i-1][j-1]+score(s[i])
score(s[i])是s[i]型号的恐怖分子的分数
情形2: s[i]!=t[j], 则
dp[i][j] = max{dp[i-1][j], dp[i][j-1]}

复杂度显然是 O(N*M)的

所以不难给出如下算法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
//#include "stdafx.h"

#include <stdio.h>
#include <string.h>
#define MAX(x,y) ((x)>(y)?(x):(y))

//#define LOCAL

const int MAX_N = 2100;
int n, dp[MAX_N][MAX_N],score[30]; // score是击中相应类型的恐怖分子的得分
char t[30],x[MAX_N], y[MAX_N]; // t是恐怖分子的种类

int main(void)
{
#ifdef LOCAL
freopen("d:\\data.in", "r", stdin);
//freopen("d:\\my.out", "w", stdout);
#endif
while(~scanf("%d", &n))
{
scanf("%s", t);
int i = 0, s;
for (int i = 0; i<n;i++) scanf("%d", &score[t[i]-'a']);
scanf("%s%s", x,y); // x是子弹序列, y是恐怖分子序列
dp[0][0] = x[0]==y[0]? score[x[0]-'a']:0;
int lenx = strlen(x); // x的长度
int leny = strlen(y); // y的长度
for (int i =1; i<lenx;i++) dp[i][0] = x[i]==y[0]?score[y[0]-'a']:dp[i-1][0]; // x[0,...,i]和 y[0]的LCS
for (int i = 1; i < leny; i++) dp[0][i] = x[0]==y[i]?score[x[0]-'a']:dp[0][i-1]; // x[0]和 y[0,...,i]的LCS
for (int i = 1; i<lenx; i++) for (int j = 1; j<leny; j++) dp[i][j] = x[i]==y[j]?(dp[i-1][j-1]+score[x[i]-'a']):MAX(dp[i-1][j], dp[i][j-1]); // dp递推
printf("%d\n", dp[lenx-1][leny-1]); // 别错误的返回dp[n-1][n-1]了
}
return 0;
}

此题容易在数组长度上犯浑~ 我因此WA了几次.

ac情况

Status Accepted
Time 156ms
Memory 18160kB
Length 1062
Lang C++
Submitted 2019-07-29 06:56:06
Shared
RemoteRunId 29954189