UVA - 11107 Life Forms (广义后缀自动机+后缀树/后缀数组+尺取)
题意:给你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 (广义后缀自动机+后缀树/后缀数组+尺取)的更多相关文章
- UVA - 11107 Life Forms (广义后缀自动机)
题意:给你n个字符串,求出在超过一半的字符串中出现的所有子串中最长的子串,按字典序输出. 对这n个字符串建广义后缀自动机,建完后每个字符串在自动机上跑一遍,沿fail树向上更新所有子串结点的出现次数( ...
- cf666E. Forensic Examination(广义后缀自动机 线段树合并)
题意 题目链接 Sol 神仙题Orz 后缀自动机 + 线段树合并 首先对所有的\(t_i\)建个广义后缀自动机,这样可以得到所有子串信息. 考虑把询问离线,然后把\(S\)拿到自动机上跑,同时维护一下 ...
- BZOJ3413: 匹配(后缀自动机 线段树合并)
题意 题目链接 Sol 神仙题Orz 后缀自动机 + 线段树合并... 首先可以转化一下模型(想不到qwq):问题可以转化为统计\(B\)中每个前缀在\(A\)中出现的次数.(画一画就出来了) 然后直 ...
- 洛谷P2178 [NOI2015]品酒大会(后缀自动机 线段树)
题意 题目链接 Sol 说一个后缀自动机+线段树的无脑做法 首先建出SAM,然后对parent树进行dp,维护最大次大值,最小次小值 显然一个串能更新答案的区间是\([len_{fa_{x}} + 1 ...
- BZOJ1396: 识别子串(后缀自动机 线段树)
题意 题目链接 Sol 后缀自动机+线段树 还是考虑通过每个前缀的后缀更新答案,首先出现次数只有一次,说明只有\(right\)集合大小为\(1\)的状态能对答案产生影响 设其结束位置为\(t\),代 ...
- [Luogu5161]WD与数列(后缀数组/后缀自动机+线段树合并)
https://blog.csdn.net/WAautomaton/article/details/85057257 解法一:后缀数组 显然将原数组差分后答案就是所有不相交不相邻重复子串个数+n*(n ...
- 洛谷P4493 [HAOI2018]字串覆盖(后缀自动机+线段树+倍增)
题面 传送门 题解 字符串就硬是要和数据结构结合在一起么--\(loj\)上\(rk1\)好像码了\(10k\)的样子-- 我们设\(L=r-l+1\) 首先可以发现对于\(T\)串一定是从左到右,能 ...
- luogu5212/bzoj2555 substring(后缀自动机+动态树)
对字符串构建一个后缀自动机. 每次查询的就是在转移边上得到节点的parent树中后缀节点数量. 由于强制在线,可以用动态树维护后缀自动机parent树的子树和. 注意一个玄学的优化:每次在执行连边操作 ...
- 模板—字符串—后缀自动机(后缀自动机+线段树合并求right集合)
模板—字符串—后缀自动机(后缀自动机+线段树合并求right集合) Code: #include <bits/stdc++.h> using namespace std; #define ...
随机推荐
- Golang 单例模式 singleton pattern
在Java中,单例模式的实现主要依靠类中的静态字段.在Go语言中,没有静态类成员,所以我们使用的包访问机制和函数来提供类似的功能.来看下下面的例子: package singleton ...
- re 正则匹配的非贪婪匹配
非贪婪匹配 将尽可能少的匹配内容,当?出现在其他的重复次数后面时会将贪婪模式改为非贪婪模式. ? 如 abc.*? abc.+? 非贪婪匹配:尽可能少的匹配{n,}?{,n}?{n,m}?*? # * ...
- java按某属性分组并计算相关属性的和。
工作中在处理集合的时候会经常遇到需要分组然后计算某属性的和,在java8中,通过stream来操作集合,还是非常方便的,像过滤(filter).分组(group).获取单个属性的值,总而言之,简单方便 ...
- PTA(Basic Level)1029.旧键盘
旧键盘上坏了几个键,于是在敲一段文字的时候,对应的字符就不会出现.现在给出应该输入的一段文字.以及实际被输入的文字,请你列出肯定坏掉的那些键. 输入格式: 输入在 2 行中分别给出应该输入的文字.以及 ...
- liunx crontab 定时访问指定url
链接主机 crontab -e 打开文件,直接输入需要执行的脚本 1 9 * * * /usr/bin/curl http://www.baidu.com 语法解析 * * * * * /usr/bi ...
- # 关于设置AUTH_USER_MODEL出现的问题
关于设置AUTH_USER_MODEL出现的问题 在运行的时候出现了一个bug: AttributeError: type object 'UserProfile' has no attribute ...
- Hive 教程(二)-认知hive
在大数据领域,hive 的位置非常重要,排名前三的大数据工具为 spark.hive.kafka 什么是hive 在大数据领域有 3 种需求场景:传输.存储.计算: hive 是一个处理海量的结构化数 ...
- idea 去除重复代码提醒
- mysql update join
随手记录一下 UPDATE information f1 LEFT JOIN topic f2 ON f1.id = f2.id SET f1.img_url = f2.img_url
- 三、redis学习(jedis连接池)
一.jedis连接池 二.jedis连接池+config配置文件 三.jedis连接池+config配置文件+util工具类 util类 public class JedisPoolUtils { / ...