字符串/序列类DP(二)
题目:P1026 统计单词个数
作为一道NOIP的中档题目,它很好地把我虐趴下了展现出了NOIP的难度水平.以至于不看题解我根本没思路花了好久好久才A掉.
思路
这道题让我学习了如何使用中的.它真是太好用了.
中有一个函数,可以用于从字符串中找到第一次出现的首字母下标.这为字符串的匹配提供了极大的便利.题目中还有一个要求就是分块.而中恰好有一个叫做,可以用来从第位开始提取出长度为的字符串.因此这道题简直是为量身定制的.
接下来我们来看题目.在得到这个字符串之后,题目要求我们分块并分别统计单词个数.那么我们就要想了:分块的方法千千万,有没有什么方法使得我们能直接求出单词个数呢?如果没有,我们有没有办法储存区间的单词个数来节约时间呢?显然,直接求出单词个数是不现实的.那我们考虑怎么预处理.因为分块的不确定性很大,一块的长度可能很长,也可能很短.因此我们不得不统计.这是一个长度平方级别的预处理,完全可以承受的住.但是我们怎么处理呢?
题目中有一个要求,说我们在选择一个单词以后它的首字母就不能再次作为另一个单词的一部分了.换句话说,对于一个新字母来说,如果它是一个单词的首字母,那么这个区间中最多多加一个单词.就像是01背包,这个字母只能取一次.因此怎样让它只取一次呢?倒序!01背包就是这么做的.我们从后往前枚举位置,当往前扩展到一个新字母的时候,我们看看有没有区间中能不能形成一个新的单词.如果有,这个区间的就加上1,注意,只加1.通过这种方式,我们就可以预处理出任何一个区间内的单词个数了.
接下来就是DP的过程了.类似于在序列中插入一些东西,我们可以设表示把前i个字母分成j组的答案.这也是整个问题的子问题.接下来我们怎么转移呢?我们来"时光倒流"一下.之前我们可能已经形成了若干组,那么这一个字母到底属于那一组呢?有两种可能,一种是自成一组,要么是和之前归为一组,而和之前的归为一组又要考虑之前一组有多少字母.我们可以通过枚举之前最后一组和倒数第二组的分界线来确定.总而言之,我们将会得到以下方程
k就是我们的断点.
通过这道题我们可以注意到string的好用字符串问题和区间DP有时会有异曲同工之处(其实这道题就是区间DP).一定要按照DP的思路,一步一步地解题,而不是面对一团乱麻叹气.
#include <cmath>
#include <queue>
#include <deque>
#include <cctype>
#include <string>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
template<class T> inline void read(T &x) {
x = 0;
char ch = getchar(), w = 0;
while (!isdigit(ch))
w = (ch =='-'), ch = getchar();
while (isdigit(ch))
x = (x << 1) + (x << 3) + (ch ^ 48), ch = getchar();
x = w ? -x : x;
return;
}
int n, m, s, tot[300][300], f[300][300];
string org, word[10];
int main() {
read(n), read(m);
org += "0";
for (int i = 1; i <= n; ++i) {
string a;
cin >> a, org += a;
}
read(s);
for (int i = 1; i <= s; ++i)
cin >> word[i];
int L = org.length() - 1;
for (int i = L; i >= 1; --i) {
for (int j = i; j >= 1; --j) {
tot[j][i] = tot[j + 1][i];
string a = org.substr(j, i - j + 1);
for (int k = 1; k <= s; ++k)
if (a.find(word[k]) == 0) {
tot[j][i]++;
break;
}
}
}
for (int i = 1; i <= L; ++i)
f[i][i] = f[i - 1][i - 1] + tot[i][i];
for (int i = 1; i <= L; ++i)
f[i][1] = tot[1][i];
for (int i = 1; i <= L; ++i) {
for (int j = 1; j <= m && j <= i; ++j) {
for (int k = j; k < i; ++k) {
f[i][j] = max(f[i][j], f[k][j - 1] + tot[k + 1][i]);
}
}
}
cout << f[L][m] << endl;
return 0;
}
【推荐】还在用 ECharts 开发大屏?试试这款永久免费的开源 BI 工具!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 理解Rust引用及其生命周期标识(下)
· 从二进制到误差:逐行拆解C语言浮点运算中的4008175468544之谜
· .NET制作智能桌面机器人:结合BotSharp智能体框架开发语音交互
· 软件产品开发中常见的10个问题及处理方法
· .NET 原生驾驭 AI 新基建实战系列:向量数据库的应用与畅想
· C# 13 中的新增功能实操
· Ollama本地部署大模型总结
· 2025成都.NET开发者Connect圆满结束
· langchain0.3教程:从0到1打造一个智能聊天机器人
· 用一种新的分类方法梳理设计模式的脉络