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

来源:互联网 发布:淘宝买游戏账号被骗 编辑:程序博客网 时间:2024/05/29 13:04

题意:给出一个字符串,求出出现了恰好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]]<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;}