P1770 万能的OIH搜索 题解
有生之年竟然能交远古题的题解,写而无憾!
题目的描述太头疼了,我详细的说一下:
给你两个字符串 <b> 和 </b>)
这里需要注意:标记之间的字符数是第一标准,其次是加粗的数量。也就是说,如果比较两个加粗处理后的句子,先看标记之间的字符,字符少的最优,如果相同,就看加粗的数量,数量少的最优。
比如拿样例的一部分来举例子:
shang wang ping an
ping an. shang wang
这里如果这样标记:
<b>ping an</b>. <b>shang wang</b>
字符间隔就不是最少的。
或者:
<b>ping an.<\b><b> shang wang</b>
虽然标记之间的间隔少了,只有
<b>ping an. shang wang</b>
就是最优的了。
了解上面之后,我们有一个思路:
首先,将输入的字符串分解,变为单词和分隔符交替的序列,检测每个单词后的分隔符是否包含标点符号。有标点的单词不能作为词语的一部分。
然后,记录需要匹配的词语,匹配时忽略大小写,但输出时保持原样。
接着进行加粗,为了使加粗最优,将相邻的需要加粗的单词合并为一个加粗段,即使合并的单词之间有标点符号也包含在加粗段内。
最后,把每个连续加粗段都只用一对<b>和</b>标记。
代码实现(有注释):
#include <bits/stdc++.h>
using namespace std;
struct W {
string t, s; // t:单词内容, s:单词后的分隔符
bool p, b; // p:分隔符中是否有标点, b:是否需要加粗
};
// 判断字符是否为字母
bool isL(char c) {
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
}
// 判断字符是否为标点符号
bool isP(char c) {
return !isL(c) && !isspace(c);
}
// 字符串转小写
string low(string x) {
string r;
for (char c : x) r += tolower(c);
return r;
}
int main() {
string k, t;
getline(cin, k);
getline(cin, t);
vector<W> kw, tw; // 关键词单词列表, 标题单词列表
string tmp, sep; // 临时存储当前单词和分隔符
// 处理关键词
for (char c : k) {
if (isL(c)) {
if (!sep.empty()) {
if (!kw.empty()) kw.back().s = sep;
sep.clear();
}
tmp += c;
} else {
if (!tmp.empty()) {
kw.push_back({tmp, "", false, false}); // 存储单词
tmp.clear();
}
sep += c;
}
}
// 处理末尾的单词和分隔符
if (!tmp.empty()) {
kw.push_back({tmp, sep, false, false});
} else if (!sep.empty() && !kw.empty()) {
kw.back().s = sep;
}
// 分词处理标题
tmp.clear();
sep.clear();
for (char c : t) {
if (isL(c)) {
if (!sep.empty()) {
if (!tw.empty()) tw.back().s = sep;
sep.clear();
}
tmp += c;
} else {
if (!tmp.empty()) {
tw.push_back({tmp, "", false, false});
tmp.clear();
}
sep += c;
}
}
if (!tmp.empty()) {
tw.push_back({tmp, sep, false, false});
} else if (!sep.empty() && !tw.empty()) {
tw.back().s = sep;
}
// 标记分隔符中是否有标点符号
for (auto& w : kw)
for (char c : w.s)
if (isP(c)) {
w.p = true;
break;
}
for (auto& w : tw)
for (char c : w.s)
if (isP(c)) {
w.p = true;
break;
}
// 匹配词语
for (int i = 0; i < (int)kw.size() - 1; i++) {
if (kw[i].p) continue; // 跳过有标点的单词
for (int j = 0; j < (int)tw.size() - 1; j++) {
if (tw[j].p) continue; // 跳过有标点的单词
// 检查是否匹配(不区分大小写)
if (low(tw[j].t) == low(kw[i].t) && low(tw[j + 1].t) == low(kw[i + 1].t)) {
tw[j].b = tw[j + 1].b = true; // 标记需要加粗
}
}
}
bool inb = false; // 当前是否在加粗段内
for (int i = 0; i < tw.size(); i++) {
if (tw[i].b) {
if (!inb) {
cout << "<b>";
inb = true;
}
cout << tw[i].t;
// 如果是最后一个单词或下一个单词不需要加粗,则结束加粗
if (i == (int)tw.size() - 1 || !tw[i + 1].b) {
cout << "</b>";
inb = false;
}
} else {
cout << tw[i].t;
}
cout << tw[i].s;
}
return 0;
}