题解:P4824 [USACO15FEB] Censoring S
题目链接
https://www.luogu.com.cn/problem/P4824
分析
讲一种比较直观的做法。
KMP 算法中,对模式串 \(t\) 的连续性有较高要求(因为要求 \(next\) 数组并通过 \(next\) 数组跳),而对文本串 \(s\) 的连续性则没有太高要求。因此可以使用链表结构维护 \(s\),每次匹配后在 \(s\) 中删除 \(t\) 即可。
代码如下。
//一直匹配,直到无法匹配为止
while(true){
bool flg=true;
for(int i=L[0].nxt,j=0;i<=n;i=L[i].nxt){
while(j!=0&&s[i]!=t[j+1])j=nxt[j];
if(s[i]==t[j+1])++j;
//匹配上了
if(j==m){
//寻找文本串中模式串的上一位
int p=i,tmp=m;
while(tmp--)p=L[p].lst;
//更新指针
L[p].nxt=L[i].nxt,L[L[i].nxt].lst=p;
flg=false;
break;
}
}
if(flg)break;
}
此算法得 \(94\) 分,相当不错了。
考虑优化。观察到上方代码中每次匹配后都会从链表的第一个节点开始重新匹配。事实上,一次删除操作影响的范围是有限的。具体地,设 \(p\) 表示链表中“缺口”前最后一个节点,\(m\) 表示模式串的长度,则链表的第 \(p-m+1\) 号节点及其以前的节点都不会受到影响,因此下一轮匹配可以从第 \(p-m+2\) 号节点开始。
细节内容见代码注释。
Code
#include<bits/stdc++.h>
#define i64 long long
using namespace std;
constexpr int N=1e6+5;
int nxt[N];
char s[N],t[N];
struct Node{int lst,nxt;}L[N];
int main(){
ios::sync_with_stdio(false),
cin.tie(nullptr),cout.tie(nullptr);
cin>>(s+1)>>(t+1);
int n=strlen(s+1),m=strlen(t+1);
//链表初始化
L[0].nxt=1;
for(int i=1;i<=n;++i)
L[i].nxt=i+1,L[i].lst=i-1;
//对模式串求 next 数组
nxt[1]=0;
for(int i=2,j=0;i<=m;++i){
while(j!=0&&t[j+1]!=t[i])j=nxt[j];
if(t[j+1]==t[i])++j;
nxt[i]=j;
}
int pos_start=1;//从第几号节点开始匹配
while(true){
bool flg=true;
for(int i=pos_start,j=0;i<=n;i=L[i].nxt){
while(j!=0&&s[i]!=t[j+1])j=nxt[j];
if(s[i]==t[j+1])++j;
//匹配上了
if(j==m){
//p1 表示“缺口”前的最后一个节点
int p1=i,tmp1=m;
while(tmp1--)p1=L[p1].lst;
L[p1].nxt=L[i].nxt,L[L[i].nxt].lst=p1;
//从 p2 开始受到影响
int p2=p1,tmp2=m-2;
while(p2>0&&tmp2--)p2=L[p2].lst;
if(p2==0)p2=L[p2].lst;
pos_start=p2;
flg=false;
break;
}
}
if(flg)break;
}
for(int i=L[0].nxt;i<=n;i=L[i].nxt)cout<<s[i];
return 0;
}
后记
本题的正常做法是使用栈,其实“下一轮匹配从第 \(p-m+2\) 号节点开始”与栈在本质上极为相似,但常数较大。

浙公网安备 33010602011771号