luoguP4824 [USACO15FEB] Censoring S 解题报告

血的教训。。。

image
传送门

题意

FJ已经根据杂志的所有文字,创建了一个字符串 S ( S 的长度保证不超过 106 ),他想删除其中的子串 T ,他将删去 S 中第一次出现的子串 T ,然后不断重复这一过程,直到 S 中不存在子串 T

注意:每次删除一个子串后,可能会出现一个新的子串 T (说白了就是删除之后,两端的字符串有可能会拼接出来一个新的子串 T )。

输出删除所有模式串后的文本串。

样例输入 #1

whatthemomooofun
moo

样例输出 #1

whatthefun

思路及错解

一开始只能看出是 KMP 的变式,没有想到如何去实现操作。后来点开题目标签一看:有个“栈”。瞬间想到可以将字符压进栈里,根据后续情况进行匹配。

第一版错解

开始思考如何实现(错解的诞生)。发现可以枚举文本串 S 的每一位,与模式串进行匹配。枚举到 Si 时,如果与栈顶相同,则将 Si 压入栈中。代码如下:

#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。

在这份代码里,我模拟出来的栈只把 S 中与 T 中相匹配的字符压了进去,并没有考虑到 S 中不与模式串相匹配的字符,仅仅只是把指针清空了。这样就会产生一个问题:

输入:

amottmooo
moo

我的输出:

att

匹配到中间的两个字符时,仅仅是指针被清空了,而栈中剩余元素依旧保存着他们的对应值。导致在越过不合法字符之后,这些字符还可以继续匹配。所以应当将所有的字符都压到栈里面,防止出现上面的情况。

第二版错解

在自己手造了几组数据仍然不能通过后,我向 LLQ 巨佬请求帮助。讨论之后发现了这个问题。改完的代码如下:

#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。

再次寻找 LLQ 巨佬,在听完我的思路和代码解释后,他给出了一组 HACK 数据:

输入

tttta
ttta

我的输出:

tttta

我的程序做法在匹配到 S 中的第 4 个字符时,会提示与 T 的不匹配,使得指针重置。再去匹配下一个字符时,就会从头开始。导致某些可以匹配上的字符串匹配失败。

正解

还是老老实实写 KMP 吧......

在处理栈的时候要注意:要记录 S 中每个字符所需要匹配的字符对应到 T 中的下标,以便在删除某些元素后可以继承前面的记录。

贴上正解代码:

#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 国际」许可协议进行许可。

posted @   Ryder00  阅读(16)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
more_horiz
keyboard_arrow_up light_mode palette
选择主题
点击右上角即可分享
微信分享提示