luoguP4824 [USACO15FEB] Censoring S 解题报告
血的教训。。。
题意
FJ已经根据杂志的所有文字,创建了一个字符串 ( 的长度保证不超过 ),他想删除其中的子串 ,他将删去 中第一次出现的子串 ,然后不断重复这一过程,直到 中不存在子串 。
注意:每次删除一个子串后,可能会出现一个新的子串 (说白了就是删除之后,两端的字符串有可能会拼接出来一个新的子串 )。
输出删除所有模式串后的文本串。
样例输入 #1
whatthemomooofun
moo
样例输出 #1
whatthefun
思路及错解
一开始只能看出是 的变式,没有想到如何去实现操作。后来点开题目标签一看:有个“栈”。瞬间想到可以将字符压进栈里,根据后续情况进行匹配。
第一版错解
开始思考如何实现(错解的诞生)。发现可以枚举文本串 的每一位,与模式串进行匹配。枚举到 时,如果与栈顶相同,则将 压入栈中。代码如下:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
int T=1,sta[1000010],now,top,ned[1000010],pla[1000010];
bool vis[1000010];
string s,t;
void work(int i){
sta[++top]=s[i];
now++;
ned[top]=now;
pla[top]=i;
//cout<<i<<" "<<now<<endl;
if(now==t.size()){
//cout<<"imin!!"<<endl;
while(now){
now--;
vis[pla[top]]=1;
top--;
}
//cout<<i<<" "<<top<<" "<<ned[top]<<endl;
now=ned[top];
}
}
void solve(){
cin>>s;
cin>>t;
for(int i=0;i<s.size();i++){
bool pd=0;
if(s[i]==t[now]) work(i),pd=1;
else now=0;
if(s[i]==t[now]&&pd==0) work(i);
}
for(int i=0;i<s.size();i++){
if(!vis[i]) cout<<s[i];
}
}
int main(){
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
while(T--){
solve();
}
return 0;
}
可是只有28pts。
在这份代码里,我模拟出来的栈只把 中与 中相匹配的字符压了进去,并没有考虑到 中不与模式串相匹配的字符,仅仅只是把指针清空了。这样就会产生一个问题:
输入:
amottmooo
moo
我的输出:
att
匹配到中间的两个字符时,仅仅是指针被清空了,而栈中剩余元素依旧保存着他们的对应值。导致在越过不合法字符之后,这些字符还可以继续匹配。所以应当将所有的字符都压到栈里面,防止出现上面的情况。
第二版错解
在自己手造了几组数据仍然不能通过后,我向 巨佬请求帮助。讨论之后发现了这个问题。改完的代码如下:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
int T=1,now,top,ned[1000010],pla[1000010];
bool vis[1000010];
string s,t;
void solve(){
cin>>s;
cin>>t;
for(int i=0;i<s.size();i++){
top++;
if(s[i]!=t[now]) now=0;
if(s[i]==t[now]){
now++;//下一个应该匹配的字符 对应到 t 中的下标
ned[top]=now;//栈顶下一个需要的字符 对应到 t 中的下标
pla[top]=i;//栈顶元素的下标
if(now==t.size()){
while(now){
now--;
vis[pla[top]]=1;//表示这个位置可以被删除掉
top--;
}
now=ned[top];//更新下一个需要的字符 所对应到 t 中的下标
}
}
}
for(int i=0;i<s.size();i++){
if(!vis[i]) cout<<s[i];
}
}
int main(){
//freopen(".in","r",stdin);
//freopen(".out","w",stdout);
while(T--){
solve();
}
return 0;
}
可是还是只有28pts。
再次寻找 巨佬,在听完我的思路和代码解释后,他给出了一组 HACK 数据:
输入
tttta
ttta
我的输出:
tttta
我的程序做法在匹配到 中的第 个字符时,会提示与 的不匹配,使得指针重置。再去匹配下一个字符时,就会从头开始。导致某些可以匹配上的字符串匹配失败。
正解
还是老老实实写 吧......
在处理栈的时候要注意:要记录 中每个字符所需要匹配的字符对应到 中的下标,以便在删除某些元素后可以继承前面的记录。
贴上正解代码:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
int T=1,n,m,pre[1000010],kmp[1000010],sta[1000010],top;
char s[1000010],t[1000010];
void prefix(){
int j=0;
for(int i=2;i<=m;i++){
while(j&&t[i]!=t[j+1]) j=pre[j];
if(t[i]==t[j+1]) j++;
pre[i]=j;
}
}
void kmpp(){
int j=0;
for(int i=1;i<=n;i++){
while(j&&s[i]!=t[j+1]) j=pre[j];
if(s[i]==t[j+1]) j++;
kmp[i]=j;
sta[++top]=i;
if(j==m){
top-=j;
j=kmp[sta[top]];
}
}
}
void solve(){
cin>>s+1;
cin>>t+1;
n=strlen(s+1);
m=strlen(t+1);
prefix();
kmpp();
for(int i=1;i<=top;i++){
cout<<s[sta[i]];
}
}
int main(){
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
while(T--){
solve();
}
return 0;
}
后记
一道题耗了我一个晚上。也领悟到很多东西。
数据结构是辅助我们维护一些数据的得力助手。碰到一些没有思路的题目,可以想一想数据结构,想一想 栈/队列/set 是否可以维护本题的数据。某些数据对应的性质是否可以与数据结构的优势结合起来,达到效果。
看到题目,也需要去思考其运用的算法的本质和部分性质,结合题目的要求,联想到解决方案。正解都是有迹可循的。要善于去发现性质,善于联想,难题都会迎刃而解。
作者:ryder
出处:https://www.cnblogs.com/ryder/p/16647757.html
版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现