poj3415 Common Substrings(后缀数组,单调栈 | 后缀自动机)
【题目链接】
http://poj.org/problem?id=3415
【题意】
A与B长度至少为k的公共子串个数。
【思路】
基本思想是将AB各个后缀的lcp-k+1的值求和。首先将两个字符串拼接起来中间用未出现的字符隔开,划分height数组,这首先保证了每一组中字符串之间的公共子串至少有k长度,组与组之间互不干扰。
问题变成了求一个组中一个A串与之前B串形成的LCP(lcp-k+1)和一个B串与之前A串形成的LCP,问题是对称的,这里先解决第一个。用一个单调栈,栈中存放两个元素分别height_top与cnt_top,分别表示到i为止的最小height和A串的数目。维护栈中元素的height从顶到底递减:每加入一个元素如果该元素比栈顶元素小则需要将tot中cnt_top个已经累计的height_top全部替换为当前元素的height(lcp是取区间最小值)。
时间复杂度为O(n)。
【代码】
#include<cstdio>
#include<cstring>
#include<iostream>
#define FOR(a,b,c) for(int a=(b);a<=(c);a++)
using namespace std; typedef long long LL;
const int maxn = + ; int s[maxn];
int sa[maxn],c[maxn],t[maxn],t2[maxn]; void build_sa(int m,int n) {
int i,*x=t,*y=t2;
for(i=;i<m;i++) c[i]=;
for(i=;i<n;i++) c[x[i]=s[i]]++;
for(i=;i<m;i++) c[i]+=c[i-];
for(i=n-;i>=;i--) sa[--c[x[i]]]=i;
for(int k=;k<=n;k<<=) {
int p=;
for(i=n-k;i<n;i++) y[p++]=i;
for(i=;i<n;i++) if(sa[i]>=k) y[p++]=sa[i]-k;
for(i=;i<m;i++) c[i]=;
for(i=;i<n;i++) c[x[y[i]]]++;
for(i=;i<m;i++) c[i]+=c[i-];
for(i=n-;i>=;i--) sa[--c[x[y[i]]]]=y[i];
swap(x,y);
p=; x[sa[]]=;
for(i=;i<n;i++)
x[sa[i]]=y[sa[i]]==y[sa[i-]] && y[sa[i]+k]==y[sa[i-]+k]?p-:p++;
if(p>=n) break;
m=p;
}
}
int rank[maxn],height[maxn];
void getHeight(int n) {
int i,j,k=;
for(i=;i<=n;i++) rank[sa[i]]=i;
for(i=;i<n;i++) {
if(k) k--;
j=sa[rank[i]-];
while(s[j+k]==s[i+k]) k++;
height[rank[i]]=k;
}
} int n,k;
char a[maxn],b[maxn];
int sta[maxn][]; int main() {
while(scanf("%d",&k)== && k) {
scanf("%s%s",a,b);
int lena=strlen(a),lenb=strlen(b);
n=;
for(int i=;i<lena;i++) s[n++]=a[i];
s[n++]=;
for(int i=;i<lenb;i++) s[n++]=b[i];
s[n]=; build_sa('z'+,n+);
getHeight(n); int top=;
LL ans=,tot=;
for(int i=;i<=n;i++) {
if(height[i]<k) top=,tot=;
else {
int cnt=;
if(sa[i-]<lena) {
cnt++; tot+=height[i]-k+;
}
while(top && height[i]<=sta[top-][]) {
top--;
tot+=(height[i]-sta[top][])*sta[top][];
cnt+=sta[top][];
}
sta[top][]=height[i],sta[top++][]=cnt;
if(sa[i]>lena) ans+=tot;
}
}
top=tot=;
for(int i=;i<=n;i++) {
if(height[i]<k) top=,tot=;
else {
int cnt=;
if(sa[i-]>lena) {
cnt++; tot+=height[i]-k+;
}
while(top && height[i]<=sta[top-][]) {
top--;
tot+=(height[i]-sta[top][])*sta[top][];
cnt+=sta[top][];
}
sta[top][]=height[i],sta[top++][]=cnt;
if(sa[i]<lena) ans+=tot;
}
}
cout<<ans<<"\n";
}
return ;
}
UPD.16/4/6
【思路】
用字符串A构造SAM,在SAM上匹配第二个字符串B,设当前匹配长度为len,且位于状态p,则当前状态中满足条件长度不小于K的公共子串的字符串个数为
sum = len-max{ K,Min(p) }+1
SAM中一个状态代表的字符串长度为一个连续区间[ Min(s),Max(s) ],Min(s)为最小长度。
这些字符串重复的次数为|right|,即right集的大小,可以递推得到,则当前状态对于答案的贡献为sum*|right|
这时候匹配的是p,还应该统计parent树中p->root的路径上的状态中满足条件的个数。
这里设一个懒标记tag[x],记录节点x需要统计的次数,最后算一遍,每次如果Max(p->fa) >= K则上传标记。
需要注意的是可能出现大写字符 =_=
相比较而言SAM的做法更好想一些。
【代码】
#include<set>
#include<cmath>
#include<queue>
#include<vector>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define trav(u,i) for(int i=front[u];i;i=e[i].nxt)
#define FOR(a,b,c) for(int a=(b);a<=(c);a++)
#define rep(a,b,c) for(int a=(b);a>=(c);a--)
using namespace std; typedef long long ll;
const int N = 2e5+; int K;
char A[N],B[N]; int get(char c)
{
if(c>='a'&&c<='z') return c-'a';
else return c-'A'+;
} struct SAM
{
int sz,last;
int ch[N][],fa[N],l[N],c[N],b[N],tag[N];
int siz[N]; ll ans; void init() {
sz=; last=++sz;
memset(l,,sizeof(l));
memset(siz,,sizeof(siz));
memset(fa,,sizeof(fa));
memset(ch,,sizeof(ch));
memset(c,,sizeof(c));
memset(tag,,sizeof(tag));
}
void Add(int c) {
int np=++sz,p=last; last=np;
l[np]=l[p]+; siz[np]=;
for(;p&&!ch[p][c];p=fa[p]) ch[p][c]=np;
if(!p) fa[np]=;
else {
int q=ch[p][c];
if(l[q]==l[p]+) fa[np]=q;
else {
int nq=++sz; l[nq]=l[p]+;
memcpy(ch[nq],ch[q],sizeof(ch[q]));
fa[nq]=fa[q];
fa[np]=fa[q]=nq;
for(;ch[p][c]==q;p=fa[p]) ch[p][c]=nq;
}
}
}
void get_right() {
FOR(i,,sz) c[l[i]]++;
FOR(i,,last) c[i]+=c[i-];
FOR(i,,sz) b[c[l[i]]--]=i;
rep(i,sz,) siz[fa[b[i]]]+=siz[b[i]];
}
ll solve(char* s) {
int len=,p=;
ans=;
for(int i=;s[i];i++) {
int c=get(s[i]);
if(ch[p][c]) {
len++; p=ch[p][c];
} else {
while(p&&!ch[p][c]) p=fa[p];
if(!p) {
p=; len=;
} else {
len=l[p]+; p=ch[p][c];
}
}
if(len>=K) {
ans+=(ll)(len-max(K,l[fa[p]]+)+)*siz[p];
if(l[fa[p]]>=K) tag[fa[p]]++;
}
}
rep(j,sz,) {
int i=b[j];
ans+=(ll)tag[i]*(l[i]-max(K,l[fa[i]]+)+)*siz[i];
if(l[fa[i]]>=K) tag[fa[i]]+=tag[i];
}
return ans;
} }sam; int main()
{
while(scanf("%d",&K)== && K) {
scanf("%s%s",A,B);
sam.init();
for(int i=;A[i];i++)
sam.Add(get(A[i]));
sam.get_right();
printf("%lld\n",sam.solve(B));
}
return ;
}
poj3415 Common Substrings(后缀数组,单调栈 | 后缀自动机)的更多相关文章
- 【BZOJ-3238】差异 后缀数组 + 单调栈
3238: [Ahoi2013]差异 Time Limit: 20 Sec Memory Limit: 512 MBSubmit: 1561 Solved: 734[Submit][Status] ...
- BZOJ_3879_SvT_后缀数组+单调栈
BZOJ_3879_SvT_后缀数组+单调栈 Description (我并不想告诉你题目名字是什么鬼) 有一个长度为n的仅包含小写字母的字符串S,下标范围为[1,n]. 现在有若干组询问,对于每一个 ...
- BZOJ_3238_[Ahoi2013]差异_后缀数组+单调栈
BZOJ_3238_[Ahoi2013]差异_后缀数组+单调栈 Description Input 一行,一个字符串S Output 一行,一个整数,表示所求值 Sample Input cacao ...
- BZOJ.4199.[NOI2015]品酒大会(后缀数组 单调栈)
BZOJ 洛谷 后缀自动机做法. 洛谷上SAM比SA慢...BZOJ SAM却能快近一倍... 显然只需要考虑极长的相同子串的贡献,然后求后缀和/后缀\(\max\)就可以了. 对于相同子串,我们能想 ...
- 【BZOJ3879】SvT 后缀数组+单调栈
[BZOJ3879]SvT Description (我并不想告诉你题目名字是什么鬼) 有一个长度为n的仅包含小写字母的字符串S,下标范围为[1,n]. 现在有若干组询问,对于每一个询问,我们给出若干 ...
- BZOJ3238 [Ahoi2013]差异 【后缀数组 + 单调栈】
题目链接 BZOJ3238 题解 简单题 经典后缀数组 + 单调栈套路,求所有后缀\(lcp\) #include<iostream> #include<cstdio> #in ...
- POJ3415 Common Substrings —— 后缀数组 + 单调栈 公共子串个数
题目链接:https://vjudge.net/problem/POJ-3415 Common Substrings Time Limit: 5000MS Memory Limit: 65536K ...
- poj 3415 Common Substrings(后缀数组+单调栈)
http://poj.org/problem?id=3415 Common Substrings Time Limit: 5000MS Memory Limit: 65536K Total Sub ...
- POJ 3415 后缀数组+单调栈
题目大意: 给定A,B两种字符串,问他们当中的长度大于k的公共子串的个数有多少个 这道题目本身理解不难,将两个字符串合并后求出它的后缀数组 然后利用后缀数组求解答案 这里一开始看题解说要用栈的思想,觉 ...
- BZOJ4199 [Noi2015]品酒大会 【后缀数组 + 单调栈 + ST表】
题目 一年一度的"幻影阁夏日品酒大会"隆重开幕了.大会包含品尝和趣味挑战两个环节,分别向优胜者颁发"首席品 酒家"和"首席猎手"两个奖项,吸 ...
随机推荐
- ssh公钥免密码登录
1,生成公钥 ssh-keygen -t rsa 2,上传至服务器 将个人电脑的公钥添加到服务器 cat id_rsa.pub >> ~/.ssh/authorized_keys 3,本地 ...
- bootstrap bootstrapTable 分页 传值问题
bootstrapTable 分页传值 配置项:将原始的 limit: params.limit, //页面大小 page: params.offset, //页码 改成 limit: params ...
- SharePoint 2010 中使用Ztree和EasyUI样式冲突问题
<style type="text/css"> /*解决ztree和SharePoint样式冲突问题*/ .ztree li a { display: inline-b ...
- JAVA数据结构-----栈
栈是Vector的一个子类,它实现了一个标准的后进先出的栈. 堆栈只定义了默认构造函数,用来创建一个空栈. 堆栈除了包括由Vector定义的所有方法,也定义了自己的一些方法. 栈常用的五个方法: bo ...
- svn中的图标解释
黄色感叹号(有冲突): --这是有冲突了,冲突就是说你对某个文件进行了修改,别人也对这个文件进行了修改,别人抢在你提交之前先提交了,这时你再提交就会被提示发生冲突,而不 允许你提交,防止你的提交覆盖了 ...
- 如何在github上fork一个项目来贡献代码以及同步原作者的修改
[-] 如何贡献自己的力量 如何让自己的项目与原作者的项目保持同步 作为一个IT人,通过github进行学习是最快的成长手 段.我们可以浏览别人的优秀代码.但只看不动手还是成长得很慢,因此为别人贡献代 ...
- CentOS安装Nexus(Maven私有库)详细配置及上传本地jar到私服
Nexus原理 Maven的原理就是将jar从远程中央仓库下载到PC磁盘的本地仓库,当本地仓库没有发现需要的jar就会去Maven默认的远程中央仓库Maven Central(由Apache维护)中寻 ...
- 结队开发项目——七巧板NABC需求分析
NABC需求分析 我们团队项目为七巧板取了个洋气的名字叫7-magic. 怀念过去,把握现在,展望未来:立足经典,勇于创新,开创一个七巧板的新时代. 特点:可以保存图片或上传至微信平台 N ...
- Netsharp快速入门(之7) 基础档案(工作区1 向导创建工作区)
作者:秋时 杨昶 时间:2014-02-15 转载须说明出处 3.5 商品开发 3.5.1 创建部件工作区 3.5.1.1 工作区向导 1.打开平台工具,选择界面管理节点下的部件工作区 ...
- 心情符号love
写点什么呢,先谢谢心情吧,算是第一个脚印了,想先把之前的一些笔记和心得迁移进来吧,以后每个月都要充实自己的知识.向大婶们看齐.走你们走过的脚印,看你们前行的身影.沿着你们留下的路,继续为后者拓宽道路. ...