题意:给你n个字符串,求出在超过一半的字符串中出现的所有子串中最长的子串,按字典序输出。

这道题算是我的一个黑历史了吧,以前我的做法是对这n个字符串建广义后缀自动机,然后在自动机上dfs,交上去AC了,然而事后发现算法假了,出了个数据把自己给hack了...

之前写的太烂了,决定重写一遍。

正确的操作是对n个串倒序建广义后缀自动机,建好以后把每个串放到自动机上跑一遍,把所有覆盖到的状态结点打上标记(每个串只标记一次,用vis判重),记录每个状态在多少个串中出现过,然后在后缀树(fail树)上按字典序dfs一遍就好了。

注意每添加一个字符串,需要把last指向根节点,而且在每次往后添加结点的时候判断当前结点是否存在过,如果存在则需要特殊处理(源自洛谷zcysky大神的思路)

复杂度$O(n\sqrt n)$,但上界很松,跑起来速度还是很快滴~

 #include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=2e5+,M=;
int n,ka;
char s[][];
struct SAM {
int fa[N],go[N][M],mxl[N],last,tot,ch[N][M],pos[N],cc[N],nc,vis[N],cnt[N],mx;
int newnode(int l,int p) {
int u=++tot;
mxl[u]=l,pos[u]=p,cnt[u]=;
memset(go[u],,sizeof go[u]);
memset(ch[u],,sizeof ch[u]);
return u;
}
void init() {tot=nc=,last=newnode(,-);}
void add(int ch) {
cc[++nc]=ch;
int p=last;
if(go[p][ch]) {
int q=go[p][ch];
if(mxl[q]==mxl[p]+)last=q;
else {
int nq=newnode(mxl[p]+,pos[q]);
memcpy(go[nq],go[q],sizeof go[q]);
fa[nq]=fa[q],fa[q]=nq;
for(; p&&go[p][ch]==q; p=fa[p])go[p][ch]=nq;
last=nq;
}
} else {
int np=last=newnode(mxl[p]+,nc);
for(; p&&!go[p][ch]; p=fa[p])go[p][ch]=np;
if(!p)fa[np]=;
else {
int q=go[p][ch];
if(mxl[q]==mxl[p]+)fa[np]=q;
else {
int nq=newnode(mxl[p]+,pos[q]);
memcpy(go[nq],go[q],sizeof go[q]);
fa[nq]=fa[q],fa[q]=fa[np]=nq;
for(; p&&go[p][ch]==q; p=fa[p])go[p][ch]=nq;
}
}
}
}
void dfs(int u) {
if(mxl[u]==mx&&cnt[u]>n/) {
for(int i=pos[u]; i>pos[u]-mxl[u]; --i)printf("%c",cc[i]+'a');
puts("");
}
for(int i=; i<M; ++i)if(ch[u][i])dfs(ch[u][i]);
}
void run() {
for(int i=; i<n; ++i) {
last=;
int l=strlen(s[i]);
reverse(s[i],s[i]+l);
for(int j=; j<l; ++j)add(s[i][j]-'a');
}
memset(vis,-,sizeof vis);
for(int i=; i<n; ++i)
for(int j=,u=; s[i][j]; u=go[u][s[i][j]-'a'],++j)
for(int v=go[u][s[i][j]-'a']; v!=&&vis[v]!=i; v=fa[v])vis[v]=i,++cnt[v];
mx=-;
for(int i=; i<=tot; ++i)if(cnt[i]>n/)mx=max(mx,mxl[i]);
for(int i=; i<=tot; ++i)ch[fa[i]][cc[pos[i]-mxl[fa[i]]]]=i;
if(!~mx)puts("?");
else {
memset(vis,,sizeof vis);
dfs();
}
}
} sam;
int main() {
while(scanf("%d",&n),n) {
ka?puts(""):++ka;
sam.init();
for(int i=; i<n; ++i)scanf("%s",s[i]);
sam.run();
}
return ;
}

还有一种做法是利用后缀数组。

把这n个串用不同的字符连接在一起求后缀数组,并给每个后缀i赋一个值a[i]表示它是哪个字符串里的。然后对排好序的后缀进行尺取并维护区间不同值的个数,一旦区间不同值的个数>n/2,就输出长度为左右端点lcp的字符串。(需要尺取两次,第一次求出最大长度,第二次输出)

但是这样做可能会有重复的串被输出,怎么去重呢?用哈希固然可以,可有没有优雅一点的做法呢?当然。只要每次输出的时候记录一下当前子串的左端点la,下次准备输出的时候和la求一次lcp,如果lcp=最大长度的话,就跳过。

复杂度$O(nlogn+n)$

 #include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e5+,mod=;
char buf[N];
int s[N],sa[N],buf1[N],buf2[N],c[N],n,m,k,rnk[N],ht[N],ST[N][],Log[N],a[N],cnt,ka;
void Sort(int* x,int* y,int m) {
for(int i=; i<m; ++i)c[i]=;
for(int i=; i<n; ++i)++c[x[i]];
for(int i=; i<m; ++i)c[i]+=c[i-];
for(int i=n-; i>=; --i)sa[--c[x[y[i]]]]=y[i];
}
void da(int* s,int n,int m=) {
int *x=buf1,*y=buf2;
x[n]=y[n]=-;
for(int i=; i<n; ++i)x[i]=s[i],y[i]=i;
Sort(x,y,m);
for(int k=; k<n; k<<=) {
int p=;
for(int i=n-k; i<n; ++i)y[p++]=i;
for(int i=; i<n; ++i)if(sa[i]>=k)y[p++]=sa[i]-k;
Sort(x,y,m),p=,y[sa[]]=;
for(int i=; i<n; ++i)y[sa[i]]=x[sa[i-]]==x[sa[i]]&&x[sa[i-]+k]==x[sa[i]+k]?p-:p++;
if(p==n)break;
swap(x,y),m=p;
}
}
void getht() {
for(int i=; i<n; ++i)rnk[sa[i]]=i;
ht[]=;
for(int i=,k=; i<n; ++i) {
if(k)--k;
if(!rnk[i])continue;
for(; s[i+k]==s[sa[rnk[i]-]+k]; ++k);
ht[rnk[i]]=k;
}
}
void initST() {
for(int i=; i<n; ++i)ST[i][]=ht[i];
for(int j=; (<<j)<=n; ++j)
for(int i=; i+(<<j)-<n; ++i)
ST[i][j]=min(ST[i][j-],ST[i+(<<(j-))][j-]);
}
int lcp(int l,int r) {
if(l==r)return n-sa[l];
if(l>r)swap(l,r);
l++;
int k=Log[r-l+];
return min(ST[l][k],ST[r-(<<k)+][k]);
}
void add(int x,int f) {
if(!x)return;
if(!c[x])++cnt;
if(!(c[x]-=f))--cnt;
}
int main() {
Log[]=-;
for(int i=; i<N; ++i)Log[i]=Log[i>>]+;
while(scanf("%d",&m),m) {
if(ka++)puts("");
memset(a,,sizeof a);
n=;
for(int i=; i<m; ++i) {
if(i)s[n++]='z'+i;
scanf("%s",buf),k=strlen(buf);
for(int j=; j<k; ++j)a[n]=i+,s[n++]=buf[j];
}
s[n]=;
da(s,n),getht(),initST();
memset(c,,sizeof c);
cnt=;
int mx=;
for(int i=,j=; i<n; ++i) {
if(!a[sa[i]])break;
for(; j<n&&cnt<=m/; ++j)add(a[sa[j]],);
add(a[sa[i]],-);
mx=max(mx,lcp(i,j-));
}
if(!mx)puts("?");
else {
for(int i=,j=,k,la=-; i<n; ++i) {
if(!a[sa[i]])break;
for(; j<n&&cnt<=m/; ++j)add(a[sa[j]],);
if(lcp(i,j-)==mx) {
if(!~la||lcp(la,j-)!=mx) {
for(k=; k<lcp(i,j-); ++k)printf("%c",s[sa[i]+k]);
puts("");
}
la=i;
}
add(a[sa[i]],-);
}
}
}
return ;
}

UVA - 11107 Life Forms (广义后缀自动机+后缀树/后缀数组+尺取)的更多相关文章

  1. UVA - 11107 Life Forms (广义后缀自动机)

    题意:给你n个字符串,求出在超过一半的字符串中出现的所有子串中最长的子串,按字典序输出. 对这n个字符串建广义后缀自动机,建完后每个字符串在自动机上跑一遍,沿fail树向上更新所有子串结点的出现次数( ...

  2. cf666E. Forensic Examination(广义后缀自动机 线段树合并)

    题意 题目链接 Sol 神仙题Orz 后缀自动机 + 线段树合并 首先对所有的\(t_i\)建个广义后缀自动机,这样可以得到所有子串信息. 考虑把询问离线,然后把\(S\)拿到自动机上跑,同时维护一下 ...

  3. BZOJ3413: 匹配(后缀自动机 线段树合并)

    题意 题目链接 Sol 神仙题Orz 后缀自动机 + 线段树合并... 首先可以转化一下模型(想不到qwq):问题可以转化为统计\(B\)中每个前缀在\(A\)中出现的次数.(画一画就出来了) 然后直 ...

  4. 洛谷P2178 [NOI2015]品酒大会(后缀自动机 线段树)

    题意 题目链接 Sol 说一个后缀自动机+线段树的无脑做法 首先建出SAM,然后对parent树进行dp,维护最大次大值,最小次小值 显然一个串能更新答案的区间是\([len_{fa_{x}} + 1 ...

  5. BZOJ1396: 识别子串(后缀自动机 线段树)

    题意 题目链接 Sol 后缀自动机+线段树 还是考虑通过每个前缀的后缀更新答案,首先出现次数只有一次,说明只有\(right\)集合大小为\(1\)的状态能对答案产生影响 设其结束位置为\(t\),代 ...

  6. [Luogu5161]WD与数列(后缀数组/后缀自动机+线段树合并)

    https://blog.csdn.net/WAautomaton/article/details/85057257 解法一:后缀数组 显然将原数组差分后答案就是所有不相交不相邻重复子串个数+n*(n ...

  7. 洛谷P4493 [HAOI2018]字串覆盖(后缀自动机+线段树+倍增)

    题面 传送门 题解 字符串就硬是要和数据结构结合在一起么--\(loj\)上\(rk1\)好像码了\(10k\)的样子-- 我们设\(L=r-l+1\) 首先可以发现对于\(T\)串一定是从左到右,能 ...

  8. luogu5212/bzoj2555 substring(后缀自动机+动态树)

    对字符串构建一个后缀自动机. 每次查询的就是在转移边上得到节点的parent树中后缀节点数量. 由于强制在线,可以用动态树维护后缀自动机parent树的子树和. 注意一个玄学的优化:每次在执行连边操作 ...

  9. 模板—字符串—后缀自动机(后缀自动机+线段树合并求right集合)

    模板—字符串—后缀自动机(后缀自动机+线段树合并求right集合) Code: #include <bits/stdc++.h> using namespace std; #define ...

随机推荐

  1. nvm 安装及操作 node版本管理

    安装 > curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.31.1/install.sh | bash 安装完成后重启下 ...

  2. 【Python开发】urllib2.urlopen超时问题

    原帖地址:http://hi.baidu.com/yss1983/item/933fbe45a09c43e01381da06 问题描述:     没有设置timeout参数,结果在网络环境不好的情况下 ...

  3. 树莓派3 安装kali注意事项(无需显示器、键鼠连接树莓派)2017/9/18更新

     kali系统树莓派专用版下载地址https://www.offensive-security.com/kali-linux-arm-images/ 官方下载页面https://www.kali.or ...

  4. PHP使用CURL抓取页面

    cURL的基本原理 curl是利用URL语法在命令行方式下工作的开源文件传输工具,他能够从互联网上获得各种各样的网络资源.简单来说,curl就是抓取页面的升级版. <?php //1.初始化,创 ...

  5. 为什么说Python采用的是基于值的内存管理模式?

    Python中的变量并不直接存储值,而是存储了值的内存地址或者引用,假如为不同变量赋值为相同值,这个值在内存中只有一份,多个变量指向同一块内存地址.

  6. Zabbix 配置Python邮件告警

    1.首先你应该注册一个163邮箱,用于发信,再注册时开启SMTP/POP3功能,使用加密密码认证. 2.在Zabbix主机上新建send.py文件,将以下代码复制编辑,设置发件用户.密码. [root ...

  7. cf 1163D Mysterious Code (字符串, dp)

    大意: 给定字符串$C$, 只含小写字母和'*', '*'表示可以替换为任意小写字母, 再给定字符串$S,T$, 求$S$在$C$中出现次数-$T$在$C$中出现次数最大值. 设$dp[i][j][k ...

  8. Java 父类的static成员在子类中的继承情况

    父类的static成员在子类中的继承状况是怎么样的呢? 昨天突然想到这个问题,刚才试验了一下,发现很容易解释,没什么值得探讨的. 首先在学习继承时,书本都没有强调static成员有什么特殊的地方. 然 ...

  9. 剑指offer-孩子们的游戏(圆圈中最后剩下的数)-知识迁移能力-python

    题目描述 每年六一儿童节,牛客都会准备一些小礼物去看望孤儿院的小朋友,今年亦是如此.HF作为牛客的资深元老,自然也准备了一些小游戏.其中,有个游戏是这样的:首先,让小朋友们围成一个大圈.然后,他随机指 ...

  10. k8s弹性伸缩概念以及测试用例

    k8s弹性伸缩概念以及测试用例 本文原文出处:https://juejin.im/post/5c82367ff265da2d85330d4f 弹性伸缩式k8s中的一大亮点功能,当负载大的时候,你可以对 ...