poj 3415 Common Substrings(后缀数组+单调栈)
http://poj.org/problem?id=3415
Time Limit: 5000MS | Memory Limit: 65536K | |
Total Submissions: 5805 | Accepted: 1911 |
Description
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 strings A, B and one integer K, we define S, a set of triples (i, j, k):
S = {(i, j, k) | k≥K, A(i, k)=B(j, k)}.
You are to give the value of |S| for specific A, B 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(后缀数组+单调栈)的更多相关文章
- poj 3415 Common Substrings —— 后缀数组+单调栈
题目:http://poj.org/problem?id=3415 先用后缀数组处理出 ht[i]: 用单调栈维护当前位置 ht[i] 对之前的 ht[j] 取 min 的结果,也就是当前的后缀与之前 ...
- poj 3415 Common Substrings——后缀数组+单调栈
题目:http://poj.org/problem?id=3415 因为求 LCP 是后缀数组的 ht[ ] 上的一段取 min ,所以考虑算出 ht[ ] 之后枚举每个位置作为右端的贡献. 一开始想 ...
- poj 3415 Common Substrings 后缀数组+单调栈
题目链接 题意:求解两个字符串长度 大于等于k的所有相同子串对有多少个,子串可以相同,只要位置不同即可:两个字符串的长度不超过1e5; 如 s1 = "xx" 和 s2 = &qu ...
- poj 3415 Common Substrings - 后缀数组 - 二分答案 - 单调栈
题目传送门 传送点I 传送点II 题目大意 给定串$A, B$,求$A$和$B$长度大于等于$k$的公共子串的数量. 根据常用套路,用一个奇怪的字符把$A$,$B$连接起来,然后二分答案,然后按mid ...
- 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 ...
- POJ3415 Common Substrings —— 后缀数组 + 单调栈 公共子串个数
题目链接:https://vjudge.net/problem/POJ-3415 Common Substrings Time Limit: 5000MS Memory Limit: 65536K ...
- POJ 3415 Common Substrings 后缀数组+并查集
后缀数组,看到网上很多题解都是单调栈,这里提供一个不是单调栈的做法, 首先将两个串 连接起来求height 求完之后按height值从大往小合并. height值代表的是 sa[i]和sa[i ...
- 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 ...
- poj 3415 Common Substrings【SA+单调栈】
把两个串中间加一个未出现字符接起来,然后求SA 然后把贡献统计分为两部分,在排序后的后缀里,属于串2的后缀和排在他前面属于串1的后缀的贡献和属于串1的后缀和排在他前面属于串2的后缀的贡献 两部分分别作 ...
- POJ 3415 Common Substrings ——后缀数组
[题目分析] 判断有多少个长度不小于k的相同子串的数目. N^2显然是可以做到的. 其实可以维护一个关于height的单调栈,统计一下贡献,就可以了. 其实还是挺难写的OTZ. [代码] #inclu ...
随机推荐
- js解析XML
//在当前页面内追加换行标签和指定的HTML内容function w( html ){ $(document.body).append("<br/>" + htm ...
- js 添加enter事件
$(function () { document.onkeydown = function (e) { var ev = document.all ? window.event : e; ) { if ...
- nativescript环境搭建
参考官网 http://docs.nativescript.org/setup/ns-cli-setup/ns-setup-win.html
- xilinx 网站资源导读
[经验整理.01]Xilinx网站资源导读 ISE11版 标签: ISE Xilinx 入门 资源 2009-09-01 20:41 序 虽然自己也曾想过,但如果不是向农要求,把曾经写过的文 ...
- Spring的servlet context和application context
Spring lets you define multiple contexts in a parent-child hierarchy. The applicationContext.xml def ...
- Access数据库在线压缩的实现方法
如果在 Access 数据库中删除数据或对象,或者在 Access 项目中删除对象,Access 数据库或 Access 项目可能会产生碎片并会降低磁盘空间的使用效率.压缩 Access 数据库或Ac ...
- Centos7最小化安装后(minimal)安装图形界面
centos7下载地址:http://mirrors.cqu.edu.cn/CentOS/7/isos/x86_64/CentOS-7-x86_64-Minimal-1511.iso 下载后用vmwa ...
- 0708_Java如何设置输入流
1.Java如何设置输入流:?(以解决看下面实例代码) 2.Java如何设置全局变量:(以解决public static即可) 3.Java为什么在做那种机试题目的时候都要设置成静态的:(以解决,因为 ...
- Appium 解决中文输入问题
- CBQW ---分组表单展示
工作流审核表单后,将表单信息展示页面中. Rest读取展示 展示方式有2 一. CBQW内容查询, 通过CBQW内容查询.分别通过设置itemstyle和header xsl ...