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 ...
随机推荐
- Android_SeekBar
xml: <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:too ...
- SQL Server 2014 AlwaysON
. 环境准备 虚拟机:ssag-bj-ad-01, ssag-bj-fc-01, ssag-bj-sql-01, ssag-bj-sql-02, ssag-sh-ad-01, ssag-sh-fc-0 ...
- MSBuild编译扩展
新增一个C#工程,用记事本打开工程文件(.csproj结尾),滚动条拉到最后,大家可以看到一段如下的代码,其中<Target Name="BeforeBuild">和& ...
- 虚拟机CentOS联网相关配置
CentOS 用ifconfig命令,只有lo,没有eth0的解决方案 如果ifconfig -a 也没发现eth0则拷贝lo自己造一个,CentOS7 命令为 ip addr 1.配置虚拟机如下图: ...
- 关于php的两个符号@和$
在写代码的时候,碰到了在函数和变量前家 @和$的的问题,于是就借这个机会,学习下php的传值和传引用这两种方式 首先 @ 运算符只对表达式有效.对新手来说一个简单的规则就是:如果能从某处得到值,就能在 ...
- Android常见开发思路
开发思路 刷新: 重新获取数据 清空list 更新适配器 关闭进度条. 加载更多 1. 重新获取数据 添加list 更新适配器 添加轮播条. 自己设计轮播条View 引入lib库文件 设置轮播条数据. ...
- listview使用checkbox批量删除出现的问题
1.选中前面的checkbox导致后的checkbox被选中 2.选中后下滑listview,再上滑时被选中的checkbox又变成未选中状态. 问题大都是因为对listview进行代码优化重用con ...
- Submine Text 3 代码自动换行
开启自动换行功能的步骤: 1.打开 Preferences -> Setting - User(设置 - 用户) 2.在再大的括号前添加如下内容即可: "word_wrap" ...
- oracle-替换 换行符和空格符
--换行或空格: )), '') --换行及空格: update tableName set columnName= ), ), '')
- Java的基本数据类型
java的基本数据类型是四类八种: 整型 byte 1字节 8位 short 2字节 16位 int 4字节 32位 long 8字节 64位 在hibernate自动映射中会根据数字长度,选 ...