Luogu P5587 打字练习 题解
Luogu P5587 打字练习 题解
写在前面
众所周知,这是一篇题解,而且是一篇黄题的题解……
它来自洛谷月赛……但是做的时候题目是不是不太一样呢 \(QAQ\) ?
传送门点这里:打字游戏,笔者写下这篇题解,希望帮助到更多的人
题解部分
直观想法
很明显思路就是你碰到一个字符后面带有'<'就不要这个字符,可以额外开个字符串存下,最后复制
但是仔细思考一下,这是不可行的,因为可能有多个‘<’连在一起。
如果还按照上面那种方式模拟,个人没有想到有什么好的办法。所以我们可能需要优化一下。
思路改进
一开始按照上面那种想法打了个 \(50\) 分,才想到是不正确的。
然后我就想到了按照链表的方式,对于一些删除'<',我们只需要把最前面删掉的字符跳掉第一个没有被删掉的字符的地方就可以。因此我才用了一个 \(pre\) 数组记录这个删除最多延伸到哪一个字符,还有一个 \(Next\) 数组,作为指针把最前面那个被删除的直接跳到第一个没被删除的,全部处理完后再进行模拟取字符即可。
实现细节
很明显上面这个方案是可行的。具体想想怎么实现,如何更新 \(pre\) 和 \(Next\) 两个数组。
下面约定我们每一行的字符串从第一位开始,以便操作。
扫描每一行 \(i\),首先对于我们碰到的一个删除(位置为 \(j\)),可能有几种情况:
-
是第一个 字符,那么 \(pre[j]=1\),\(Next[j]=j+1\)(其实就是\(j=1\))的情况;
-
不是第一个字符,但是 \(pre[j-1]\)不为0。这说明前面的删除是连续的,所以我们要多删除一个字符。
那么怎么操作呢?其实很简单,你只要:\(pre[j]=pre[j-1]-1\),\(Next[pre[j]]=j+1\)即可。
到这里就会有人有疑问了,为什么总是让 \(Next[]=j+1\)?
那么我们继续考虑,如果j+1仍然是删除,那么 \(Next\) 必定会跳到再下一位,否则 \(j+1\) 即为答案。
但是要注意特判 \(pre[j-1]-1=0\) 的情况,此时应直接赋值为 \(1\)。
- 不是第一个字符,且\(pre[j-1]=0\),那么直接让 \(pre[j]=j-1,Next[pre[j]]=j+1\)即可。
- 然后我们就完成了本题。如果还有不理解的,请结合代码理解下吧:
\(Code:\)
#include<bits/stdc++.h>
using namespace std;
const int N = 1e4 + 10;
string a[N], b[N], x, y;
int Next[N], pre[N];
int main() {
int n = 0, m = 0;
for(int i = 1; ; i++) {
getline(cin, x);
if(x == "EOF") break;
a[++n] = x;
}
for(int i = 1; ; i++) {
getline(cin, x);
if(x == "EOF") break;
b[++m] = x;
}
for(int i = 1; i <= n; i++) {
memset(Next, 0, sizeof Next);
memset(pre, 0, sizeof pre);
int size = a[i].size();
y = a[i], x = " " + a[i], a[i] = x, x = "";
bool flag = 0;
for(int j = 1; j <= size; j++)
if(a[i][j] == '<') {
if(j == 1) pre[j] = 1;
else if(pre[j - 1]) {
if(pre[j - 1] > 1)
pre[j] = pre[j - 1] - 1;
else pre[j] = 1;
} else if(!pre[j - 1])
pre[j] = j - 1;
Next[pre[j]] = j + 1;
flag = 1;
}
if(flag == 0) {
a[i] = y;
continue;
}
for(int j = 1; j <= size; j++) {
if(!Next[j]) x += a[i][j];
else j = Next[j] - 1;
}
a[i] = x;
}
for(int i = 1; i <= n; i++) {
memset(Next, 0, sizeof Next);
memset(pre, 0, sizeof pre);
int size = b[i].size();
y = b[i], x = " " + b[i], b[i] = x, x = "";
bool flag = 0;
for(int j = 1; j <= size; j++)
if(b[i][j] == '<') {
if(j == 1) pre[j] = 1;
else if(pre[j - 1]) {
if(pre[j - 1] > 1)
pre[j] = pre[j - 1] - 1;
else pre[j] = 1;
} else if(!pre[j - 1])
pre[j] = j - 1;
Next[pre[j]] = j + 1;
flag = 1;
}
if(flag == 0) {
b[i] = y;
continue;
}
for(int j = 1; j <= size; j++) {
if(!Next[j]) x += b[i][j];
else j = Next[j] - 1;
}
b[i] = x;
}
int ans = 0;
for(int i = min(n, m); i; i--) {
// 这里采用倒序是为了避免重复计算 min(n,m)
int len = min(a[i].size(), b[i].size());
if(len == 0) continue;
for(int j = 0; j < len; j++)
if(a[i][j] == b[i][j]) ans++;
}
int T; scanf("%d", &T);
ans = (int)((double)ans * 60.0 / (double) T + 0.5);
printf("%d", ans);
return 0;
}
当然会有更加优秀的写法,不过我认为个人理解更加重要。
写在后面
月赛的时候只拿到了 \(50pts\),原因竟是月赛题面说范文里没有'<'?
我还有什么可以说的呢?……