题目链接

\(Description\)

给出两个串\(S,T\),求\(T\)在\(S\)中出现了多少次。出现是指。可以有\(3\)次(\(3\)个字符)不匹配(修改使其匹配)。

\(Solution\)

一个套路的做法是构造多项式(CF528D),对每个字符c单独考虑,\(f[i]=[S[i]可匹配c],g[i]=[T[i]==c]\)。

然后\(F=f*g\),可以得到每个位置往后长\(m\)的串中有多少个位置\(S,T\)都匹配了\(c\)。如果某个位置匹配字符数\(\geq m-3\),则以它为左端点的串可行。

FFT/NTT实现,常数好也许能过。

SA做法:枚举\(S\)的每个位置\(i\),设当前匹配\(T\)匹配到\(j\),得到两个串的ht数组后我们可以\(O(1)\)求出\(LCP(suf[i],suf[j])\),直接\(j+=LCP\)就行了。

如果某个位置不匹配,可以至多用\(3\)次机会直接跳过去。所以实际枚举\(j\)的次数只有\(5\)。

复杂度\(O(Tn\log n)\)。

SAM做法:得到parent树后,直接在上面DFS,如果能匹配则走,不能匹配则用一次次数。走了\(m\)步则加上该点的贡献(出现过多少次)。

复杂度\(O(Tn)\)。

还有某种神奇的Hash做法。。好像复杂度比较优。


SAM:

//9224kb	1624ms
#include <cstdio>
#include <cstring>
#include <algorithm>
const int N=2e5+5; struct Suffix_Automaton
{
int n,Ans,tot,las,son[N][4],fa[N],len[N],cnt[N],tm[N],A[N],ref[233];
char s[N]; Suffix_Automaton() {tot=las=1;}
void Insert(int c)
{
int np=++tot,p=las;
len[las=np]=len[p]+1, cnt[np]=1;
for(; p&&!son[p][c]; p=fa[p]) son[p][c]=np;
if(!p) fa[np]=1;
else
{
int q=son[p][c];
if(len[q]==len[p]+1) fa[np]=q;
else
{
int nq=++tot; len[nq]=len[p]+1;
memcpy(son[nq],son[q],sizeof son[q]);
fa[nq]=fa[q], fa[q]=fa[np]=nq;
for(; son[p][c]==q; p=fa[p]) son[p][c]=nq;
}
}
}
void Build()
{
tot=las=1;
ref['A']=0, ref['T']=1, ref['G']=2, ref['C']=3;
memset(tm,0,sizeof tm);//! 你前缀和了→_→
memset(cnt,0,sizeof cnt), memset(son,0,sizeof son); scanf("%s",s+1); int l=strlen(s+1);
for(int i=1; i<=l; ++i) Insert(ref[s[i]]);
for(int i=1; i<=tot; ++i) ++tm[len[i]];
for(int i=1; i<=l; ++i) tm[i]+=tm[i-1];
for(int i=1; i<=tot; ++i) A[tm[len[i]]--]=i;
for(int i=tot,x=A[i]; i; x=A[--i]) cnt[fa[x]]+=cnt[x];
}
void DFS(int x,int use,int l)
{
if(l==n) return (void)(Ans+=cnt[x]);
for(int i=0; i<4; ++i)
if(son[x][i])
if(ref[s[l]]==i) DFS(son[x][i],use,l+1);
else if(use<3) DFS(son[x][i],use+1,l+1);
}
void Query()
{
scanf("%s",s), n=strlen(s);
Ans=0, DFS(1,0,0), printf("%d\n",Ans);
}
}sam; int main()
{
int T; scanf("%d",&T);
while(T--) sam.Build(), sam.Query();
return 0;
}

SA:

//19768kb	5976ms(好慢...)
#include <cstdio>
#include <cstring>
#include <algorithm>
const int N=2e5+7; int MAP[233],sa[N],sa2[N],rk[N],tm[N],ht[N],lg2[N],mn[18][N];
char s[N]; void Get_SA(int n)
{
int *x=rk,*y=sa2,m=5;
for(int i=0; i<=m; ++i) tm[i]=0;
for(int i=1; i<=n; ++i) ++tm[x[i]=MAP[s[i]]];
for(int i=1; i<=m; ++i) tm[i]+=tm[i-1];
for(int i=n; i; --i) sa[tm[x[i]]--]=i;
for(int k=1,p=0; k<n; k<<=1,m=p,p=0)
{
for(int i=n-k+1; i<=n; ++i) y[++p]=i;
for(int i=1; i<=n; ++i) if(sa[i]>k) y[++p]=sa[i]-k; for(int i=0; i<=m; ++i) tm[i]=0;
for(int i=1; i<=n; ++i) ++tm[x[i]];
for(int i=1; i<=m; ++i) tm[i]+=tm[i-1];
for(int i=n; i; --i) sa[tm[x[y[i]]]--]=y[i]; std::swap(x,y), x[sa[1]]=p=1;
for(int i=2; i<=n; ++i)
x[sa[i]]=(y[sa[i]]==y[sa[i-1]]&&y[sa[i]+k]==y[sa[i-1]+k])?p:++p;
if(p>=n) break;
}
for(int i=1; i<=n; ++i) rk[sa[i]]=i;
ht[1]=0;
for(int i=1,k=0,p; i<=n; ++i)
{
if(rk[i]==1) continue;
if(k) --k;
p=sa[rk[i]-1];
while(i+k<=n && p+k<=n && s[i+k]==s[p+k]) ++k;
ht[rk[i]]=k;
}
}
void Init_ST(int n)
{
for(int i=1; i<=n; ++i) mn[0][i]=ht[i];
for(int j=1; j<=lg2[n]; ++j)
for(int i=1; i<=n; ++i)
mn[j][i]=std::min(mn[j-1][i],mn[j-1][i+(1<<j-1)]);
}
inline int LCP(int l,int r)
{
l=rk[l], r=rk[r]; if(l>r) std::swap(l,r);
++l;
int k=lg2[r-l+1];
return std::min(mn[k][l],mn[k][r-(1<<k)+1]);
} int main()
{
MAP['A']=1, MAP['T']=2, MAP['C']=3, MAP['G']=4, MAP['Z']=5;
lg2[1]=0;
for(int i=2; i<=200005; ++i) lg2[i]=lg2[i>>1]+1; int T; scanf("%d",&T);
while(T--)
{
int l,n;
scanf("%s",s+1), s[l=strlen(s+1)+1]='Z';
scanf("%s",s+l+1), n=strlen(s+1);
Get_SA(n), Init_ST(n);
int ans=0;
for(int i=1,m=n-l,lim=l-m; i<=lim; ++i)
{
for(int j=1,t=0; t<=3; )
{
if(j>m) {++ans; break;}
else if(s[i+j-1]!=s[l+j]) ++j, ++t;
else j+=LCP(i+j-1,l+j);
}
}
printf("%d\n",ans);
}
return 0;
}

BZOJ.4892.[TJOI2017]DNA(后缀自动机/后缀数组)的更多相关文章

  1. BZOJ 4892 [Tjoi2017]dna 哈希+二分

    自己简直是傻死了...对于位置想错了... 二分出来的是LCP长度$+1$,即每一次二分出来的最后一个点都是失配的,而就算失配也会跳过这个点:所以当$k<=3$且模式串$s2$的指针$>l ...

  2. poj 1743 Musical Theme 后缀自动机/后缀数组/后缀树

    题目大意 直接用了hzwer的题意 题意:有N(1 <= N <=20000)个音符的序列来表示一首乐曲,每个音符都是1..88范围内的整数,现在要找一个重复的主题."主题&qu ...

  3. [模板] 后缀自动机&&后缀树

    后缀自动机 后缀自动机是一种确定性有限状态自动机, 它可以接收字符串\(s\)的所有后缀. 构造, 性质 翻译自毛子俄罗斯神仙的博客, 讲的很好 后缀自动机详解 - DZYO的博客 - CSDN博客 ...

  4. bzoj 3277: 串 & bzoj 3473: 字符串【后缀自动机||后缀数组】

    建一个广义后缀自动机(每加完一个串都返回root),在parent树上dpsum记录合法长度,打着时间戳往上跳,最后每个串在自动机上跑一变统计答案即可. 后缀数组理解起来可能方便一点,但是难写,就只说 ...

  5. 回文树&后缀自动机&后缀数组

    KMP,扩展KMP和Manacher就不写了,感觉没多大意思.   之前感觉后缀自动机简直可以解决一切,所以不怎么写后缀数组.   马拉车主要是通过对称中心解决问题,有的时候要通过回文串的边界解决问题 ...

  6. SPOJ705 Distinct Substrings (后缀自动机&后缀数组)

    Given a string, we need to find the total number of its distinct substrings. Input T- number of test ...

  7. POJ3080 POJ3450Corporate Identity(广义后缀自动机||后缀数组||KMP)

    Beside other services, ACM helps companies to clearly state their “corporate identity”, which includ ...

  8. SPOJ SUBLEX - Lexicographical Substring Search 后缀自动机 / 后缀数组

    SUBLEX - Lexicographical Substring Search Little Daniel loves to play with strings! He always finds ...

  9. UVA - 11107 Life Forms (广义后缀自动机+后缀树/后缀数组+尺取)

    题意:给你n个字符串,求出在超过一半的字符串中出现的所有子串中最长的子串,按字典序输出. 这道题算是我的一个黑历史了吧,以前我的做法是对这n个字符串建广义后缀自动机,然后在自动机上dfs,交上去AC了 ...

随机推荐

  1. MySQL— 基础

    目录 一.MySQL概述 二.下载安装 三.数据库操作 四.数据表操作 五.表内容操作 一.MySQL概述 MySQL是一个关系型数据库管理系统,由瑞典MySQL AB 公司开发,目前属于 Oracl ...

  2. linux系统下安装redis及配置

    下载Redis redis-3.2.11.tar.gz 解压编译 tar xzf redis-3.2.11.tar.gz cd redis-3.2.11 make 编译完成之后,可以看到解压文件red ...

  3. 高级 Java 必须突破的 10 个知识点!

    1.Java基础技术体系.JVM内存分配.垃圾回收.类装载机制.性能优化.反射机制.多线程.网络编程.常用数据结构和相关算法. 2.对面向对象的软件开发思想有清晰的认识.熟悉掌握常用的设计模式. 3. ...

  4. mysql数据库查询库中所有表所占空间大小

    SELECT CONCAT(table_schema,'.',table_name) AS 'TABLE_NAME', CONCAT(, ),'M') AS 'ROW_SIZE', CONCAT( ) ...

  5. Docker+STF在ubuntu下测试环境搭建(详细搭建步骤及踩坑记录)

    一.什么是OpenSTF? STF又称OpenSTF,它是一个手机设备管理平台,可以对手机进行远程管理.调试.远程手机桌面监控等操作.这个系统类似于目前很流行的云测服务比如Testin,虽然网页上提供 ...

  6. nagios系列(七)nagios通过自定义脚本的方式监控mysql主从同步

    nagios监控mysql主从同步 起因:nagios可能监控到mysql服务的运行情况,但确不能监控mysql的主从复制是否正常:有时候,同步已经停止,但管理人员却不知道. 登陆mysql从服务器, ...

  7. S5PV210 PWM定时器

    第一节 S5PV210的PWM定时器S5PV210共有5个32bit的PWM定时器,其中定时器0.1.2.3有PWM功能,定时器4没有输出引脚.PWM定时器使用PCLK_PSYS作为时钟源,相关知识可 ...

  8. OneNET麒麟座应用开发之八:采集大气压力等环境参数

    采集大气压力和温度也是核算大气标准状况下的各种数据的必须参数,为此我们必须知道压力和温度才能计算标准状况下的各种参数,于此我们需要一个既能检测压力也能检测温度的元件. 1.硬件概述 MS5837压力传 ...

  9. hdu 1542 线段树+扫描线 学习

    学习扫描线ing... 玄学的东西... 扫描线其实就是用一条假想的线去扫描一堆矩形,借以求出他们的面积或周长(这一篇是面积,下一篇是周长) 扫描线求面积的主要思想就是对一个二维的矩形的某一维上建立一 ...

  10. Fiddler抓包3-查看get与post请求

    前言 前面两篇关于Fiddler抓包的一些基本配置,配置完之后就可以抓到我们想要的数据了,接下来就是如何去分析这些数据. 本篇以博客园的请求为例,简单分析get与post数据有何不一样,以后也能分辨出 ...