题意:给你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. Opencv中的WMesh

    费了半天劲,终于把这个WMesh类搞懂了,可惜效果不佳,比Matlab中的mesh差多了. 使用WMesh前,需要有一个Mesh对象,Mesh是三维数据点的基本几何信息.颜色信息.索引信息等集成的对象 ...

  2. Android开发 调试环境

    我们这里有3种调试方法,Unity Remote,Android Studio和第三方模拟器 准备工作 (1)Android Studio:安装Google USB Driver (2)手机安装Uni ...

  3. flask 之(五) --- 对象|钩子|拆分

    内置对象 request: 请求的所有信息 session   服务端会话技术的接口 config:    当前项目的配置信息,模板中可以直接使用 g:global 在单次请求过程中,实现全局数据共享 ...

  4. jmeter-JDBC 连接池设置

  5. 谷歌云SSH开启root密码登陆

    废话不多说,开始教程 1.先选择从浏览器打开ssh连接服务器连接登录成功后,输入以下命令 sudo -i #切换到root passwd #修改密码 然后会要求输入新密码,然后再重复一次密码,输入密码 ...

  6. aws和ufile挂载数据盘EBS

    aws的话挂载的ebs需要格式化,参考:https://docs.aws.amazon.com/zh_cn/AWSEC2/latest/UserGuide/ebs-using-volumes.html ...

  7. .Net Core 中使用NLog作为日志中间件

    ⒈安装相关依赖 NLog NLog.Web.AspNetCore ⒉在项目的根目录中创建NLog配置文件 <?xml version="1.0" encoding=" ...

  8. JVM 线上故障排查基本操作 (转)

    前言 对于后端程序员,特别是 Java 程序员来讲,排查线上问题是不可避免的.各种 CPU 飚高,内存溢出,频繁 GC 等等,这些都是令人头疼的问题.楼主同样也遇到过这些问题,那么,遇到这些问题该如何 ...

  9. Redis利用Pipeline加速查询速度的方法

    1. RTT Redis 是一种基于客户端-服务端模型以及请求/响应协议的TCP服务.这意味着通常情况下 Redis 客户端执行一条命令分为如下四个过程: 发送命令 命令排队 命令执行 返回结果 客户 ...

  10. leecode刷题(27)-- 合并k个排序链表

    leecode刷题(27)-- 合并k个排序链表 合并k个排序链表 合并 k 个排序链表,返回合并后的排序链表.请分析和描述算法的复杂度. 示例: 输入: [ 1->4->5, 1-> ...