Diorvh

导航

【题解】CF 1200E. Compress Words

1200E. Compress Words

题意:给n个字符串(大小写字母+数字),从左向右依次合并,每次合并时会去掉后面字符串的前缀中与已有字符串后缀重复的部分。输出合并之后的字符串。

\(1\leq n\leq 10^5\),保证\(\Sigma len(s_n)\leq 10^6\)

如:

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;
}

posted on 2019-11-05 09:29  diorvh  阅读(332)  评论(0编辑  收藏  举报