【题解】CF 1200E. Compress Words
1200E. Compress Words
题意:给n个字符串(大小写字母+数字),从左向右依次合并,每次合并时会去掉后面字符串的前缀中与已有字符串后缀重复的部分。输出合并之后的字符串。
1≤n≤105,保证Σlen(sn)≤106。
如:
5
sample please mplease ein out
结果:sampleaseinout
思路:
名词解释:
当前字符串:第i个字符串。
已有字符串:前i-1个字符串合并之后的结果。
首先我排除的是后缀数组,因为匹配后缀的话,必须每次都对合并之后的字符串重新建后缀数组,显然会爆。
那么有没有可以利用已有前缀继续建立后缀信息的呢?有!比如后缀自动机,本来就是一个一个字符往里添加的,所以一个(假)做法就出现了:
- 对第一个串建SAM。
- 第二个串在SAM上匹配,处理如下:
- 已经匹配到了自动机终止状态(last节点):则说明已经匹配到了已有字符串的一个后缀,则当前字符串已经匹配的部分就不用再添加到SAM里了。
- 当前位置有出边:走向出边。
- 当前位置没有出边,要跳fail:说明当前字符串不可能匹配到后缀,则将当前字符串全部添加到SAM里。
- 对后面的串依次处理,输出答案。
但显然这道题卡SAM,字符集太大,开int数组就MLE,开map就T。
我竟然写完了才反应过来这个问题。
于是只能考虑用KMP来匹配,实际上思路和用后缀自动机类似。每次将当前串当做模式串,建立fail数组,之后去匹配已有字符串(直接对齐模式串和已有字符串的最后一个字符即可)。如果匹配过程中i走到了已有字符串的末尾,则找到了模式串前缀和已有字符串后缀的重复部分,只将这之后的部分加入到结果中即可。
复杂度呢?每个字符串都建立了一遍fail数组,加起来是O(n)的。每次匹配的长度是当前字符串的长度,加起来也是O(n)。
AC代码:KMP。109ms,5888KB
#include<bits/stdc++.h>
using namespace std;
const int M=1e6+20;
int fail[M],len,lenp;
char p[M],s2[M];
void make_fail(){
for (int i=1,j=0;p[i];++i){
while (j&&p[i]!=p[j])
j=fail[j-1];
if (p[i]==p[j])
fail[i]=++j;
else
fail[i]=0;
}
}
int kmp(){
int ans=0;
for (int i=max(len-lenp+1,0),j=0;s2[i];++i){
while(j&&s2[i]!=p[j])
j=fail[j-1];
if (s2[i]==p[j]){
++j;
if (i==len)
return j;
}
}
return 0;
}
int main(){
int n;
scanf("%d",&n);
scanf("%s",s2);
getchar();
len=strlen(s2)-1;
for(int z=2;z<=n;++z){
lenp=0;
while((p[lenp]=getchar())){
if (p[lenp]==' '||p[lenp]=='\n')
break;
++lenp;
}
p[lenp]='\000';
make_fail();
int en=kmp();
for(int i=en;i<lenp;++i)
s2[++len]=p[i];
}
for(int i=0;i<=len;++i)
putchar(s2[i]);
putchar('\n');
return 0;
}
另外把后缀自动机的也贴出来。开int数组MLE,开map的话160MB左右但T了。
#include<bits/stdc++.h>
#include<unordered_map>
using namespace std;
const int M=1e6+20;
char s[M],s2[M];
struct SAM{
int last,cnt,fa[M<<1],len[M<<1];
int ch[M<<1][123];
void ins(char c){
int p=last,np=++cnt;
last=np,len[np]=len[p]+1;//,size[np]=1;
for(;p&&!ch[p][c];p=fa[p])
ch[p][c]=np;
if(!p)
fa[np]=1;
else{
int q=ch[p][c];
if(len[p]+1==len[q])
fa[np]=q;
else{
int nq=++cnt;
len[nq]=len[p]+1;
memcpy(ch[nq],ch[q],sizeof ch[q]);
fa[nq]=fa[q],fa[q]=fa[np]=nq;
for(;ch[p][c]==q;p=fa[p])
ch[p][c]=nq;
}
}
}
void build(){
int n;
scanf("%d",&n);
last=cnt=1;
scanf("%s",s2);
int pe=strlen(s2)-1;
for(int i=0;i<=pe;++i)
ins(s2[i]);
getchar();
for (int z=2;z<=n;++z){
int lenn=0;
while((s[lenn]=getchar())){
if (s[lenn]==' '||s[lenn]=='\n')
break;
++lenn;
}
s[lenn]='\000';
int now=1,i=0;
bool v=false;
for(;i<lenn;i++)
if (now==last){
v=true;
break;
}
else if (ch[now][s[i]])
now=ch[now][s[i]];
else
break;
if (now==last)
v=true;
if (!v)
i=0;
for (int j=i;j<lenn;++j)
ins(s[j]),s2[++pe]=s[j];
}
for(int i=0;i<=pe;++i)
putchar(s2[i]);
putchar('\n');
}
}sam;
int main(){
sam.build();
return 0;
}
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 解答了困扰我五年的技术问题
· 为什么说在企业级应用开发中,后端往往是效率杀手?
· 用 C# 插值字符串处理器写一个 sscanf
· Java 中堆内存和栈内存上的数据分布和特点
· 开发中对象命名的一点思考
· 为什么说在企业级应用开发中,后端往往是效率杀手?
· DeepSeek 解答了困扰我五年的技术问题。时代确实变了!
· 本地部署DeepSeek后,没有好看的交互界面怎么行!
· 趁着过年的时候手搓了一个低代码框架
· 推荐一个DeepSeek 大模型的免费 API 项目!兼容OpenAI接口!