NC16696 [NOIP2001]统计单词个数

题目链接

题目

题目描述

给出一个长度不超过200的由小写英文字母组成的字母串(约定;该字串以每行20个字母的方式输入,且保证每行一定为20个)。要求将此字母串分成k份( 1 < k ≤ 40 ),且每份中包含的单词个数加起来总数最大(每份中包含的单词可以部分重叠。当选用一个单词之后,其第一个字母不能再用。例如字符串this中可包含this和is,选用this之后就不能包含th)。

单词在给出的一个不超过6个单词的字典中。

要求输出最大的个数。

输入描述

每组的第一行有2个正整数(p,k)
p表示字串的行数,k表示分为k个部分。
接下来的p行,每行均有20个字符。
再接下来有1个正整数s,表示字典中单词个数。(1 ≤ s ≤ 6 )
接下来的s行,每行均有1个单词。

输出描述

1个整数,分别对应每组测试数据的相应结果。

示例1

输入

1 3
thisisabookyouareaoh
4
is
a
ok
sab

输出

7

题解

知识点:枚举,线性dp,字符串。

这道题是 \(k\) 串最大和的变形,加和部分变为了字符串最多匹配的数量。

因此先预处理出 \(sum[i][j]\) 表示为区间 \([i,j]\) 的最大匹配数。实现上需要逆推,因为要求是第一个字符不一样的匹配才算,所以逆推一个字符后看从这个字符出发有没有匹配的,有就加一。

\(dp[i][j]\) 为考虑到 \(i\) 个字符,共有 \(j\) 串。转移方程为:

\[dp[i][j] = \max(dp[i-1][j],dp[u][j-1] + sum[u+1][i]),0 \leq u \leq i-1 \]

表示不选从 \(s[i]\) 出发的串,选 \(s[u+1,i]\) 作为第 \(j\) 串。

由于不能选空串,因此初始化负无穷,并把初态 \(j = 0\) 赋值 \(0\)

时间复杂度 \(O(k|s|^2)\)

空间复杂度 \(O(|s|^2 + k|s| + m)\)

代码

#include <bits/stdc++.h>

using namespace std;

int sum[207][207], dp[207][47];
string dir[10];

int main() {
    std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int p, k;
    cin >> p >> k;
    string str;
    while (p--) {
        string tmp;
        cin >> tmp;
        str += tmp;
    }
    int n = str.length();
    str = '?' + str;
    int s;
    cin >> s;
    for (int i = 1;i <= s;i++)  cin >> dir[i];
    for (int j = n;j >= 1;j--) {
        for (int i = j;i >= 1;i--) {
            sum[i][j] = sum[i + 1][j];
            bool ok = false;
            string tmp = str.substr(i, j - i + 1);
            for (int k = 1;k <= s;k++)
                ok |= tmp.find(dir[k]) == 0;
            sum[i][j] += ok;
        }
    }
    memset(dp, -0x3f, sizeof(dp));
    for (int i = 0;i <= n;i++) dp[i][0] = 0;
    for (int i = 1;i <= n;i++) {
        for (int j = 1;j <= k;j++) {
            dp[i][j] = dp[i - 1][j];
            for (int u = 0;u < i;u++)
                dp[i][j] = max(dp[i][j], dp[u][j - 1] + sum[u + 1][i]);
        }
    }
    cout << dp[n][k] << '\n';
    return 0;
}
posted @ 2022-08-12 10:02  空白菌  阅读(30)  评论(0编辑  收藏  举报