您的位置:首页 > 其它

HDU 6194 string string string :后缀数组+单调队列 | 后缀自动机

2017-09-13 02:25 489 查看
题意:给出一个字符串,求出出现了恰好k次的子串的个数。

题解:恰好k次 = 至少k次 - 至少k+1次。答案转化为求至少出现k次的子串个数统计。构造好后缀数组以及很重要的Height数组之后。用一个k-1的窗子去滑动。窗子里边放着k-1个Height值(Height[ i+1 ],Height[ i+2 ],……,Height[ i+k ]),这样k-1个值就联系了k个后缀(SA[ i ],SA[ i+1 ],……,SA[ i+k ]),显然要求出这k-1个Height值的最小值,这个最小值就是这k个后缀的极大公共前缀了,假设是x,那么长度为1..x的前缀都在这个窗子里面出现了k次,也就是说他们都是符合答案的子串了。但是要考虑前边已经重复统计过了1..x中的一部分,考虑前一个窗子(Height[
i ],Height[ i+1 ],……,Height[ i+k-1 ]),讨论前面这个窗子的最小值y:1、y<x,那么说明上一个窗子的最小值一定是Height[ i ] ,因此上一个窗子统计了1..Height[ i ]部分,这一次需要统计Height[ i ]..x部分。2、y>x,那么就是说明前面窗子的极大公共前缀长度 比 这一个窗子的 极大公共前缀长,而这两个窗子有(i+1,i+2,,……,i+k-1)这k-2个元素是一样的。因此考虑所有的k个值(Height[ i ],Height[ i+1 ],……,Height[
i+k-1 ],Height[ i+k ]),1..x必然是他们的公共前缀,那么由于y>x,前面一个窗子已经统计完了1..y的部分,1..x的部分被计算在前一个窗子里面了,这一次不需要计算,为了和上边一个式子统一起来,我们考虑Height[ i ],显然有Height[ i ]>=y>x,进而有Height[ i ]>x。

因此结论是:用k-1的窗子从2开始滑动,维护窗子的最小值,然后每个窗子和刚刚移出窗子的那个Height作比较,如果min>Height,就统计min-Height。相反不统计。维护最小值用单调队列复杂度最低(priority_queue是nlogn,手写数组模拟是n)。构造后缀数组用倍增方法,复杂度是nlogn,因此整体复杂度是nlogn。

PS:CSDN上很火的一个五分钟学后缀数组的博客,代码是错误的,只需要把我下边这个AC的代码的统计答案部分移植到那个版本的代码中去,然后提交到HDU 6194就可以愉快的得到一个WA。假博客坑了我两整天的时间。但是我也没有找到那个代码具体错在哪里,感觉可能是他的基数排序比较丑,把自己弄挂了(第二关键字排序之后,肯定是n个位置都有一个序号,但如果在中间加入一个assert(p==n)就可以0ms得到WA,所以他的第二关键字排序有问题)。自己也是拿这道题入门的后缀数组,都怪自己太菜……

Code:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MaxN=1e5+100;
const int MAXN = MaxN;
int cntA[MaxN],cntB[MaxN],tsa[MAXN],A[MAXN],B[MAXN];
int sa[MAXN],Rank[MAXN],h[MAXN];
char ch[MAXN];
struct Node{
int val,index;
Node(int val_,int index_):val(val_),index(index_){
}
bool operator < (const Node b)const{
if (val==b.val){
return b.index<index;
}
return b.val<val;
}
};
priority_queue<Node>pq;
void GetSa(char *ch,int *sa,int *rank,int n){

for(int i=0;i<MaxN;i++)  cntA[i]=0;
for(int i=1;i<=n;i++)   cntA[ch[i]]++;
for(int i=1;i<=MaxN;i++) cntA[i]+=cntA[i-1];
for(int i=n;i;i--)  sa[cntA[ch[i]]--]=i;
rank[sa[1]]=1;
for(int i=2;i<=n;i++){
rank[sa[i]]=rank[sa[i-1]];
if(ch[sa[i]]!=ch[sa[i-1]])  rank[sa[i]]++;
}
for(int l=1;rank[sa
]<n;l<<=1){
for(int i=0;i<MaxN;i++)  cntA[i]=0;
for(int i=0;i<MaxN;i++)  cntB[i]=0;
for(int i=1;i<=n;i++){
cntA[A[i]=rank[i]]++;
cntB[B[i]=(i+l<=n)?rank[i+l]:0]++;
}
for(int i=1;i<MaxN;i++)   cntB[i]+=cntB[i-1];
for(int i=n;i;i--)  tsa[cntB[B[i]]--]=i;
for(int i=1;i<MaxN;i++)  cntA[i]+=cntA[i-1];
for(int i=n;i;i--)  sa[cntA[A[tsa[i]]]--]=tsa[i];
rank[sa[1]]=1;
for(int i=2;i<=n;i++){
rank[sa[i]]=rank[sa[i-1]];
if(A[sa[i]]!=A[sa[i-1]] || B[sa[i]]!=B[sa[i-1]])    rank[sa[i]]++;
}
}
}

void GetHeight(char *ch,int *sa,int *rank,int *height,int n){

GetSa(ch,sa,rank,n);
for(int i=1,j=0;i<=n;i++){
if(j)   j--;
while(ch[i+j]==ch[sa[rank[i]-1]+j]) j++;
height[rank[i]]=j;
}
}
int GetK(int k,int n){
int ans=0;
k--;
if(k==0){
for(int i=1;i<=n;++i)   ans=ans+(n-sa[i]+1-h[i]);
return ans;
}
while (!pq.empty())pq.pop();
for (int i=2;i<=n;i++){
while (!pq.empty()&&pq.top().index<i-k+1)pq.pop();
pq.push(Node(h[i],i));
if (i>k){
int top = pq.top().val;
int last = h[i-k];
ans +=max(0,top-last);
}
}
return ans;
}

void Run(){
int n,k;
scanf("%d",&k);
scanf("%s",ch+1);
n=strlen(ch+1);
GetHeight(ch,sa,Rank,h,n);
printf("%d\n",GetK(k,n)-GetK(k+1,n));
}
int main(){
int T;
scanf("%d",&T);
while(T--){
Run();
}
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息