当前位置: 首页 > news >正文

P1026 [NOIP 2001 提高组] 统计单词个数

题目描述

给出一个长度不超过 200 的由小写英文字母组成的字母串(该字串以每行 20 个字母的方式输入,且保证每行一定为 20 个)。要求将此字母串分成 k 份,且每份中包含的单词个数加起来总数最大。

每份中包含的单词可以部分重叠。当选用一个单词之后,其第一个字母不能再用。例如字符串 this 中可包含 this 和 is,选用 this 之后就不能包含 th

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

要求输出最大的个数。

输入格式

每组的第一行有两个正整数 p,k。 p 表示字串的行数,k 表示分为 k 个部分。

接下来的 p 行,每行均有 20 个字符。

再接下来有一个正整数 s,表示字典中单词个数。 接下来的 s 行,每行均有一个单词。

输出格式

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

输入输出样例

输入 #1

1 3
thisisabookyouareaoh
4
is
a
ok
sab

输出 #1

7

说明/提示

【数据范围】
对于 100% 的数据,2≤k≤40,1≤s≤6。

【样例解释】 划分方案为 this / isabookyoua / reaoh

【题目来源】

NOIP 2001 提高组第三题


题目分析

本题要求我们在一个字符串中划分k个部分,使得每个部分包含的单词总数最大。这是一个典型的动态规划问题,结合字符串处理技巧,需要仔细设计状态转移方程。

问题重述

给定一个字符串s,将其分成k段,每段至少包含一个字符。已知若干单词,统计每段中包含的单词数(单词可以重叠,多次出现多次计数),求如何分割才能使所有段的单词数之和最大。

解题思路

基本思路

  1. 预处理:计算字符串中每个位置开始的单词出现情况

  2. 动态规划

    • 状态定义:dp[i][j]表示前i个字符分成j段的最大单词数

    • 状态转移:dp[i][j] = max(dp[t][j-1] + count[t+1][i]),其中t从j-1到i-1

  3. 单词统计:预处理count数组,count[i][j]表示子串s[i..j]中的单词数

算法选择

动态规划是解决此类分段优化问题的标准方法,时间复杂度为O(n^2*k),对于题目给定的数据范围(n≤200,k≤40)完全足够。

完整代码

cpp

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;const int N = 210, K = 50;
int p, k, n, m;
string s, word[10];
int dp[N][K], cnt[N][N];
bool vis[N][N];// 检查s[l..r]是否以某个单词开头
bool check(int l, int r) {string sub = s.substr(l, r-l+1);for (int i = 1; i <= m; i++) {if (sub.find(word[i]) == 0) return true;}return false;
}// 预处理cnt数组
void preprocess() {for (int r = 1; r <= n; r++) {for (int l = r; l >= 1; l--) {cnt[l][r] = cnt[l+1][r];if (check(l, r)) cnt[l][r]++;}}
}int main() {cin >> p >> k;s = " ";for (int i = 1; i <= p; i++) {string tmp; cin >> tmp;s += tmp;}cin >> m;for (int i = 1; i <= m; i++) cin >> word[i];n = s.size() - 1;preprocess();// 初始化dp数组for (int i = 1; i <= n; i++) dp[i][1] = cnt[1][i];// 动态规划for (int j = 2; j <= k; j++) {for (int i = j; i <= n; i++) {for (int t = j-1; t < i; t++) {dp[i][j] = max(dp[i][j], dp[t][j-1] + cnt[t+1][i]);}}}cout << dp[n][k] << endl;return 0;
}

代码详解

数据结构

  1. 字符串处理

    cpp

    string s, word[10]; // s是主串,word存储单词

  2. 动态规划数组

    cpp

    int dp[N][K]; // dp[i][j]表示前i个字符分成j段的最大单词数

  3. 计数数组

    cpp

    int cnt[N][N]; // cnt[l][r]表示子串s[l..r]中的单词数

预处理函数

cpp

void preprocess() {for (int r = 1; r <= n; r++) {for (int l = r; l >= 1; l--) {cnt[l][r] = cnt[l+1][r];if (check(l, r)) cnt[l][r]++;}}
}
  • 从右向左填充cnt数组

  • 利用之前计算结果优化当前计算

检查函数

cpp

bool check(int l, int r) {string sub = s.substr(l, r-l+1);for (int i = 1; i <= m; i++) {if (sub.find(word[i]) == 0) return true;}return false;
}
  • 检查子串s[l..r]是否以某个单词开头

  • 使用string的find方法简化实现

动态规划核心

cpp

for (int j = 2; j <= k; j++) {for (int i = j; i <= n; i++) {for (int t = j-1; t < i; t++) {dp[i][j] = max(dp[i][j], dp[t][j-1] + cnt[t+1][i]);}}
}
  • 外层循环枚举分段数j

  • 中层循环枚举前i个字符

  • 内层循环枚举分割点t

  • 状态转移方程:dp[i][j] = max(dp[t][j-1] + cnt[t+1][i])

复杂度分析

  • 时间复杂度

    • 预处理:O(n^2 * m * L),L是单词平均长度

    • 动态规划:O(n^2 * k)

  • 空间复杂度:O(n^2 + n*k)

优化思考

  1. 单词查找优化

    • 可以使用Trie树加速单词查找

    • 或者预处理所有单词的哈希值

  2. 空间优化

    • dp数组可以优化为一维,因为每次只用到j-1的状态

    • cnt数组可以只保留必要部分

  3. 并行计算

    • 预处理阶段可以并行处理不同区间的检查

测试样例

样例1

输入:

text

1 3
thisisabookyouareaoh
4
is
a
ok
sab

输出:

text

7

解释:
最佳分割方式为"this isabookyouareaoh",各部分单词数为3+3+1=7

样例2

输入:

text

2 2
abcde fghij
2
abc
fgh

输出:

text

2

解释:
分割为"abcde"和"fghij",每部分各含1个单词

边界情况

  1. k=1

    • 整个字符串作为一段

  2. k=n

    • 每个字符作为一段

  3. 单词长度=1

    • 需要正确处理单字符单词的匹配

总结

本题通过动态规划有效解决了字符串分割优化问题,关键点在于:

  1. 合理设计dp状态表示

  2. 预处理子串单词数加速计算

  3. 正确处理状态转移方程

代码实现清晰高效,展示了动态规划在字符串处理中的典型应用。该解法可以扩展到类似的分段优化问题中。

扩展思考

  1. 单词不可重叠

    • 需要修改cnt数组的计算方式

    • 记录单词出现位置避免重复计数

  2. 最小化单词数

    • 修改状态转移为取最小值

  3. 多字符串处理

    • 可以扩展为在多文本中寻找最优分割

这个题目很好地训练了动态规划思维和字符串处理能力,是算法学习的优秀范例。

http://www.dtcms.com/a/316433.html

相关文章:

  • 计算机网络:详解路由器如何转发子网数据包
  • Java JDBC连接池深度解析与实战指南
  • SAP PP CK466
  • 解决docker load加载tar镜像报json no such file or directory的错误
  • jQuery中Ajax返回字符串处理技巧
  • Window.structuredClone() 指南
  • 基于深度学习钢铁表面缺陷检测系统(yolov8/yolov5)
  • 《算法导论》第 3 章 - 函数的增长
  • 本地配置运行https协议
  • Spring依赖注入:从原理到实践的自学指南
  • Linux 调度器函数sched_*系统调用及示例
  • 【数据结构入门】单链表和数组的OJ题(1)
  • 基于ARM+FPGA光栅数据采集卡设计
  • OpenCV学习 day5
  • 从「同步」到「异步」:用 aiohttp 把 Python 网络 I/O 榨到极致
  • Python--OCR(2)
  • 微算法科技(NASDAQ:MLGO)基于量子重加密技术构建区块链数据共享解决方案
  • 算法438. 找到字符串中所有字母异位词
  • 算法第31天|动态规划:最后一块石头的重量Ⅱ、目标和、一和零
  • 二分查找
  • 算法训练营day41 动态规划⑧ 121. 122.123.买卖股票的最佳时机1.2.3
  • 常用技术资料链接
  • Spring小细节
  • oelove奥壹新版v11.7旗舰版婚恋系统微信原生小程序源码上架容易遇到的几个坑,避免遗漏参数白屏显示等问题
  • Electron-updater + Electron-builder + IIS + NSIS + Blockmap 完整增量更新方案
  • 物联网后端系统架构:从基础到AI驱动的未来 - 第十章:AI促进IOT领域发生革命式发展
  • WebRTC采集模块技术详解
  • 阿里云百炼平台创建智能体-上传文档
  • Mysql使用Canal服务同步数据->ElasticSearch
  • Linux-环境变量