题目链接: Funny Substrings
大致题意
有n个操作, 询问执行完n个操作后, 最后的字符串中包含多少个"haha".
操作有两种:
操作①: x := s
把字符串s赋值给变量x.
操作②: x = a + b
把变量x 修改为 变量a + 变量b, 其中两个变量相加表示两个字符串的拼接.
解题思路
字符串合并 (下文中没有前缀和一词, 想表达的意思均为 前缀 和 xxxxxx)
首先我们明确一点, 如果字符串的长度只有1, 那么把这个字符串自我合并n次后, 长度可达2n. 因此直接暴力模拟是不成立的.
考虑到题目中需要我们计算出最终的字符串中含有多少个"haha", 我们发现对于每个操作①类型的字符串, 可以直接求出有多少个"haha"串.
当遇到操作②类型的字符串时(假设进行操作a = b + c
), 对于a中的"haha"串, 我们可以分成三类去看: b中原有的, c中原有的, b和c拼接后新产生的. 我们重点需要考虑拼接后新产生的"haha", 一定是由b串的一个严格后缀+c串的一个严格前缀得到的(严格前后缀: 一个非空的前后缀子串).
到此为止, 我们的大概思路就成型了, 对于每一个变量, 我们需要记录这个变量的前缀字符串, 后缀字符串, 当前字符串内的答案.
对于操作①, (假设进行操作 x := a
) 我们记录下a的前缀 和 后缀, 以及 a中的haha数量,. { pre, suf, ans }.
对于操作②, (假设进行操作a = b + c
) 我们取b的前缀作为a的前缀, c的后缀作为a的后缀, 以及两者原有贡献和新贡献作为a的答案. { b.pre, c.suf, b.ans + c.ans + newans }
此时有一个很重要的问题, 我们需要记录多长的前缀 和 后缀?
考虑到我们记录前缀, 后缀的目的是, 计算两个串合并后产生的新"haha"串, 用到的是严格前后缀, 因此我们记录3个字符即可.(因为是严格前后缀, 最大拆分为3, 1).
综上所述, 我们可以保证我们的统计方式不重不漏.
关于代码方面, 和上述思路的实现略有改变(为了好写), 详细看注释即可.
AC代码
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 1; i <= (n); ++i)
using namespace std;
typedef long long ll;
struct node { string str; ll cou; }; //str存储前后缀, 前3个为前缀, 后3个为后缀, 保证长度<=6, cou为结果.
int fact(const string& s) { //计算s串中有多少个"haha"
int res = 0;
for (int i = 0; i < s.length(); ++i) {
if (s.substr(i, 4) == "haha") res++;
}
return res;
}
string getpre(const string& s) { return s.substr(0, 3); } //得到前缀
string getsuf(const string& s) { return s.substr(max<int>(0, s.size() - 3), 3); } //得到后缀
node merge(const node& a, const node& b) { //操作②, 合并字符串
return { getpre(a.str) + getsuf(b.str), a.cou + b.cou + fact(getsuf(a.str) + getpre(b.str)) };
}
int main()
{
ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
int t; cin >> t;
while (t--) {
unordered_map<string, node> mp; //哈希表, 映射变量名和结构体
int n; cin >> n;
rep(i, n) {
string a, b; cin >> a >> b;
if (b.front() == ':') { //操作①
cin >> b;
mp[a] = { b, fact(b) }; //题目保证串长<=5, 因此直接存储即可
}
else {
string c; cin >> b >> c >> c;
mp[a] = merge(mp[b], mp[c]);
}
if (i == n) cout << mp[a].cou << endl;
}
}
return 0;
}
大模拟, 狗都不写! 哦, 50行不到啊, 那没事儿了!