http://poj.org/problem?id=3415

Common Substrings
Time Limit: 5000MS   Memory Limit: 65536K
Total Submissions: 5805   Accepted: 1911

Description

A substring of a string T is defined as:

T(ik)=TiTi+1...Ti+k-1, 1≤ii+k-1≤|T|.

Given two strings AB and one integer K, we define S, a set of triples (ijk):

S = {(ijk) | kKA(ik)=B(jk)}.

You are to give the value of |S| for specific AB and K.

Input

The input file contains several blocks of data. For each block, the first line contains one integer K, followed by two lines containing strings A and B, respectively. The input file is ended by K=0.

1 ≤ |A|, |B| ≤ 105 1 ≤ K ≤ min{|A|, |B|} Characters of A and B are all Latin letters.

Output

For each case, output an integer |S|.

Sample Input

2
aababaa
abaabaa
1
xx
xx
0

Sample Output

22
5 思路:

【题意】

给定一个数k

在给定两个字符串A,B

求三元组(i,j,k)(表示从A的第i位起和从B的j位起长度为k的字符串相同)的个数

【输入】

多组数据

每组数据第一行为k

接下来两行分别为A、B(长度均不大于100000)

【输出】

对于每组数据,输出一个数,表示三元组的个数

后缀数组应用题之一

后缀数组的用法很经典

将两个字符串之间加一个没出现过的字符连接起来

然后求height

对于B的一个后缀,对应每一个A的后缀若他们的公共前缀长为l,若l大于等于k,则会有l-k+1种三元组

这个统计便是本题的难点

如果枚举A的后缀和B的后缀,那么复杂度为n^2,对于本题明显是不可以的

所以要另寻途径

根据论文的提示要使用单调栈,我想到了一种实现

按rank的顺序,统计每一个B的后缀名次之前的A的后缀有关的三元组数量

然后再统计每一个A的后缀名次之前的B的后缀有关的三元组数量

两者之和便是答案

将问题划分为了两个等价的问题

那么完成每一个问题的时候从左到右扫描,每一个只跟已扫描过的有关

这时候便可以动态维护了

首先,与许多后缀数组题目中类似的,这里存在三元组的后缀们是聚集在一起的

按height可以分组

对于每一组从左到右扫描,则需要维护一个栈和一个值

这个栈是栈内元素与当前元素公共前缀长度递增的一个栈,值是若当前当前后缀之前的三元组个数

根据最长公共前缀的性质,rank越相近,则公共前缀长度越大,所以从左到右扫描的后缀与当前后缀的公共前缀长度是递减的

栈内每个元素表示之前有total个后缀与当前元素公共前缀长度为sim,明显,若当前height小于某些栈内元素的sim,则需要修改这些元素和值

据此调整,每个元素最多进出栈一次,复杂度为O(N)

题解部分转自:http://blog.csdn.net/weixinding/article/details/7222882

AC代码:(用数组模拟栈)

 #include <iostream>
#include <stdio.h>
#include<string.h> #define maxn 200010 #define cls(x) memset(x, 0, sizeof(x)) int wa[maxn],wb[maxn],wv[maxn],wss[maxn]; int cmp(int *r,int a,int b,int l){return r[a]==r[b]&&r[a+l]==r[b+l];} //倍增算法
void da(char *r,int *sa,int n,int m)
{
cls(wa);
cls(wb);
cls(wv);
int i,j,p,*x=wa,*y=wb,*t; //基数排序
for(i=;i<m;i++) wss[i]=;
for(i=;i<n;i++) wss[x[i]=r[i]]++;
for(i=;i<m;i++) wss[i]+=wss[i-];
for(i=n-;i>=;i--) sa[--wss[x[i]]]=i; // 在第一次排序以后,rank数组中的最大值小于p,所以让m=p。整个倍增算法基本写好,代码大约25行。
for(j=,p=;p<n;j*=,m=p)
{
//接下来进行若干次基数排序,在实现的时候,这里有一个小优化。基数排序要分两次,第一次是对第二关键字排序,第二次是对第一关键字排序。对第二关键字排序的结果实际上可以利用上一次求得的sa直接算出,没有必要再算一次
for(p=,i=n-j;i<n;i++) y[p++]=i;
for(i=;i<n;i++) if(sa[i]>=j) y[p++]=sa[i]-j; //其中变量j是当前字符串的长度,数组y保存的是对第二关键字排序的结果。然后要对第一关键字进行排序,
for(i=;i<n;i++) wv[i]=x[y[i]];
for(i=;i<m;i++) wss[i]=;
for(i=;i<n;i++) wss[wv[i]]++;
for(i=;i<m;i++) wss[i]+=wss[i-];
for(i=n-;i>=;i--) sa[--wss[wv[i]]]=y[i]; //这样便求出了新的sa值。在求出sa后,下一步是计算rank值。
for(t=x,x=y,y=t,p=,x[sa[]]=,i=;i<n;i++)
x[sa[i]]=cmp(y,sa[i-],sa[i],j)?p-:p++;
}
} int rank[maxn],height[maxn]; //得到height数组:排名相邻的两个后缀的最长公共前缀
void calheight(char *r,int *sa,int n)
{
cls(rank);
cls(height);
int i,j,k=;
for(i=;i<n;i++) rank[sa[i]]=i;
for(i=;i<n;height[rank[i++]]=k)
for(k?k--:,j=sa[rank[i]-];r[i+k]==r[j+k];k++);
return;
} char ca[maxn];
int sa[maxn];
int N,len;
int minh[maxn];
int stk[maxn],cnt[maxn]; int main()
{
int i,k;
while (~scanf("%d",&k)&&k)
{
scanf("%s",ca);
len= N = strlen(ca);
ca[N] = '#';
scanf("%s",ca+N+);
N = strlen(ca);
da(ca, sa, N, );
calheight(ca,sa,N);
for(i=;i<=N;i++) //将LCP-k+1预处理到height数组中
{
if(height[i]>=k) height[i]-=k-;
else height[i]=;
}
__int64 ans,temp,size;
ans=temp=;
int top=-;
for(i=;i<=N;i++)
{
for(size=;top>-&&stk[top]>height[i];top--)
{
size+=cnt[top];
temp+=(height[i]-stk[top])*cnt[top];
}
stk[++top]=height[i];
cnt[top]=size;
if(sa[i-]<len)
{
temp+=height[i];
cnt[top]++;
}
if(sa[i]>len)
ans+=temp;
}
temp=;
top=-;
for(i=;i<=N;i++)
{
for(size=;top>-&&stk[top]>height[i];top--)
{
size+=cnt[top];
temp+=(height[i]-stk[top])*cnt[top];
}
stk[++top]=height[i];
cnt[top]=size;
if(sa[i-]>len)
{
temp+=height[i];
cnt[top]++;
}
if(sa[i]<len)
ans+=temp;
}
printf("%I64d\n",ans);
}
return ;
}

附:

利用栈容器TLE(搞不懂为神马!!!):

<过了两天,终于被我发现为嘛超时了,G++提交AC,C++提交TLE,我了个去啊,坑爹,坑爹啊,目测原因C++容器处理过程耗时太多了>

(C++)TLE代码<G++提交AC>:

 #include <iostream>
#include <stdio.h>
#include<string.h>
#include <stack> using namespace std; #define maxn 200010 #define cls(x) memset(x, 0, sizeof(x)) struct Nod
{
int height;
int s;
}node; int wa[maxn],wb[maxn],wv[maxn],wss[maxn]; int cmp(int *r,int a,int b,int l){return r[a]==r[b]&&r[a+l]==r[b+l];} //倍增算法
void da(char *r,int *sa,int n,int m)
{
cls(wa);
cls(wb);
cls(wv);
int i,j,p,*x=wa,*y=wb,*t; //基数排序
for(i=;i<m;i++) wss[i]=;
for(i=;i<n;i++) wss[x[i]=r[i]]++;
for(i=;i<m;i++) wss[i]+=wss[i-];
for(i=n-;i>=;i--) sa[--wss[x[i]]]=i; // 在第一次排序以后,rank数组中的最大值小于p,所以让m=p。整个倍增算法基本写好,代码大约25行。
for(j=,p=;p<n;j*=,m=p)
{
//接下来进行若干次基数排序,在实现的时候,这里有一个小优化。基数排序要分两次,第一次是对第二关键字排序,第二次是对第一关键字排序。对第二关键字排序的结果实际上可以利用上一次求得的sa直接算出,没有必要再算一次
for(p=,i=n-j;i<n;i++) y[p++]=i;
for(i=;i<n;i++) if(sa[i]>=j) y[p++]=sa[i]-j; //其中变量j是当前字符串的长度,数组y保存的是对第二关键字排序的结果。然后要对第一关键字进行排序,
for(i=;i<n;i++) wv[i]=x[y[i]];
for(i=;i<m;i++) wss[i]=;
for(i=;i<n;i++) wss[wv[i]]++;
for(i=;i<m;i++) wss[i]+=wss[i-];
for(i=n-;i>=;i--) sa[--wss[wv[i]]]=y[i]; //这样便求出了新的sa值。在求出sa后,下一步是计算rank值。
for(t=x,x=y,y=t,p=,x[sa[]]=,i=;i<n;i++)
x[sa[i]]=cmp(y,sa[i-],sa[i],j)?p-:p++;
}
} int rank[maxn],height[maxn]; //得到height数组:排名相邻的两个后缀的最长公共前缀
void calheight(char *r,int *sa,int n)
{
cls(rank);
cls(height);
int i,j,k=;
for(i=;i<n;i++) rank[sa[i]]=i;
for(i=;i<n;height[rank[i++]]=k)
for(k?k--:,j=sa[rank[i]-];r[i+k]==r[j+k];k++);
return;
} char ca[maxn];
int sa[maxn];
int N,len;
int minh[maxn]; int main()
{
int i,k;
while (~scanf("%d",&k)&&k)
{
scanf("%s",ca);
len= N = strlen(ca);
ca[N] = '#';
scanf("%s",ca+N+);
N = strlen(ca);
da(ca, sa, N, );
calheight(ca,sa,N);
for(i=;i<=N;i++) //将LCP-k+1预处理到height数组中
{
if(height[i]>=k) height[i]-=k-;
else height[i]=;
}
stack<Nod> stk;
__int64 ans,temp;
int s=;
ans=;
temp=;
for(i=;i<=N;i++) //这里从1遍历到N 等号不能去掉,需要保证pop能彻底
{
s=;
while(stk.size()>&&height[i]<stk.top().height)
{
s+=stk.top().s;
temp+=(height[i]-stk.top().height)*stk.top().s;
stk.pop();
}
if(sa[i-]<len)
{
temp+=height[i];
s++;
}
if(sa[i]>len) ans+=temp;
node.height = height[i];
node.s = s;
stk.push(node);
}
while(!stk.empty()) stk.pop();
temp=;
for(i=;i<=N;i++)
{
s=;
while(stk.size()>&&height[i]<stk.top().height)
{
s+=stk.top().s;
temp+=(height[i]-stk.top().height)*stk.top().s;
stk.pop();
}
if(sa[i-]>len)
{
temp+=height[i];
s++;
}
if(sa[i]<len) ans+=temp;
node.height = height[i];
node.s = s;
stk.push(node);
}
while(!stk.empty()) stk.pop();
printf("%I64d\n",ans);
}
return ;
}

poj 3415 Common Substrings(后缀数组+单调栈)的更多相关文章

  1. poj 3415 Common Substrings —— 后缀数组+单调栈

    题目:http://poj.org/problem?id=3415 先用后缀数组处理出 ht[i]: 用单调栈维护当前位置 ht[i] 对之前的 ht[j] 取 min 的结果,也就是当前的后缀与之前 ...

  2. poj 3415 Common Substrings——后缀数组+单调栈

    题目:http://poj.org/problem?id=3415 因为求 LCP 是后缀数组的 ht[ ] 上的一段取 min ,所以考虑算出 ht[ ] 之后枚举每个位置作为右端的贡献. 一开始想 ...

  3. poj 3415 Common Substrings 后缀数组+单调栈

    题目链接 题意:求解两个字符串长度 大于等于k的所有相同子串对有多少个,子串可以相同,只要位置不同即可:两个字符串的长度不超过1e5; 如 s1 = "xx" 和 s2 = &qu ...

  4. poj 3415 Common Substrings - 后缀数组 - 二分答案 - 单调栈

    题目传送门 传送点I 传送点II 题目大意 给定串$A, B$,求$A$和$B$长度大于等于$k$的公共子串的数量. 根据常用套路,用一个奇怪的字符把$A$,$B$连接起来,然后二分答案,然后按mid ...

  5. POJ - 3415 Common Substrings(后缀数组求长度不小于 k 的公共子串的个数+单调栈优化)

    Description A substring of a string T is defined as: T( i, k)= TiTi+1... Ti+k-1, 1≤ i≤ i+k-1≤| T|. G ...

  6. POJ3415 Common Substrings —— 后缀数组 + 单调栈 公共子串个数

    题目链接:https://vjudge.net/problem/POJ-3415 Common Substrings Time Limit: 5000MS   Memory Limit: 65536K ...

  7. POJ 3415 Common Substrings 后缀数组+并查集

    后缀数组,看到网上很多题解都是单调栈,这里提供一个不是单调栈的做法, 首先将两个串 连接起来求height   求完之后按height值从大往小合并.  height值代表的是  sa[i]和sa[i ...

  8. POJ - 3415 Common Substrings (后缀数组)

    A substring of a string T is defined as: T( i, k)= TiTi +1... Ti+k -1, 1≤ i≤ i+k-1≤| T|. Given two s ...

  9. poj 3415 Common Substrings【SA+单调栈】

    把两个串中间加一个未出现字符接起来,然后求SA 然后把贡献统计分为两部分,在排序后的后缀里,属于串2的后缀和排在他前面属于串1的后缀的贡献和属于串1的后缀和排在他前面属于串2的后缀的贡献 两部分分别作 ...

  10. POJ 3415 Common Substrings ——后缀数组

    [题目分析] 判断有多少个长度不小于k的相同子串的数目. N^2显然是可以做到的. 其实可以维护一个关于height的单调栈,统计一下贡献,就可以了. 其实还是挺难写的OTZ. [代码] #inclu ...

随机推荐

  1. 西门子PLC两线制,四线制

    1 一.对于控制系统模块:两线制,四线制信号都只有两根线接入模件,区别在于: 两线制信号的这两根线一正一负,不带提供信号电流,而且提供供电电压:一般流量,压力,液位等等的信号常用两线制信号,但也要根据 ...

  2. Android_TextVIew_flow_ex1

    xml文件: <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns ...

  3. C语言结构体的强制类型转换

    陈浩师兄03年的一篇博客<用C写有面向对象特点的程序>描述了用C语言来实现类似C++类继承的方法,这样方法的核心要点就是结构体的强制类型转换,让我来简单分析分析C语言中的结构体强制类型转换 ...

  4. 用Java原子变量的CAS方法实现一个自旋锁

    为了防止无良网站的爬虫抓取文章,特此标识,转载请注明文章出处.LaplaceDemon/ShiJiaqi. http://www.cnblogs.com/shijiaqi1066/p/5999610. ...

  5. D3中path各指令的含义

    svg.append('path').attr({ id: 'mypath', d: 'M50 100Q350 50 350 250Q250 50 50 250' }) path 的指令有: 指令 参 ...

  6. Linux下mplayer源代码安装方法

    http://www.linuxidc.com/Linux/2007-08/6820.htm 1下载安装mplayer需要的各种软件 去这里下载http://www.mplayerhq.hu/MPla ...

  7. C#基于AE组件二次开发常见问题

    由于本人从事的是在.net平台下进行GIS的二次开发,所以第一篇博文就说一下:我最近在项目中出现的常见的问题,如果能够给大家增加一点点便利,也是我的荣幸,如果大家对于这次博文有什么意见和建议,欢迎大家 ...

  8. 性能更好的js动画实现方式——requestAnimationFrame

    本文转载,原文地址:http://www.cnblogs.com/2050/p/3871517.html 用js来实现动画,我们一般是借助setTimeout或setInterval这两个函数,css ...

  9. Centos7安装并配置mysql5.6完美教程

    Centos7安装并配置mysql5.6完美教程 Centos7将默认数据库mysql替换成了Mariadb,对于我们这些还想使用mysql的开发人员来说并不是一个好消息.然而,网上关于Linux安装 ...

  10. [技术翻译]构建现代化的 Objective-C (上)

    我的技术博客经常被流氓网站恶意爬取转载.请移步原文:http://www.cnblogs.com/hamhog/p/3561514.html,享受整齐的排版.有效的链接.正确的代码缩进.更好的阅读体验 ...