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:
题解:恰好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; }
相关文章推荐
- HDU 6194 string string string [后缀数组]
- HDU 6194 String String String 后缀数组 正好出现K次的子串个数 CSU1632 至少出现2次的子串个数
- HDU 6194 string string string 后缀数组 + RMQ(线段树)
- 2017沈阳网络赛 1001 HDU 6194 string string string(后缀自动机 出现k次的子串个数)
- string string string hdu 6194 (后缀数组做法)
- HDU 6194 string string string【后缀数组】
- HDU 6194 string string string(后缀自动机)
- HDU 6194 后缀数组
- 【后缀自动机】[HDU 4641]K-string
- HDU 4641 K-string 2013年多校第4场J题 后缀自动机
- [二分 后缀自动机 单调队列优化DP] BZOJ 2806 [Ctsc2012]Cheat
- [BZOJ2806] [CTSC2012] Cheat - 后缀自动机 - DP - 单调队列
- hdu 4641 K-string(后缀自动机 + 并查集)
- 【bzoj2806】[Ctsc2012]Cheat 广义后缀自动机+二分+单调队列优化dp
- HDU 4622 Reincarnation 后缀数组 或 后缀自动机
- bzoj2806 [Ctsc2012]Cheat(后缀自动机+单调队列优化DP)
- BZOJ1396:识别子串(后缀自动机+单调队列)
- BZOJ.2806.[CTSC2012]Cheat(广义后缀自动机 DP 单调队列)
- BZOJ2806 [Ctsc2012]Cheat 【后缀自动机 + 二分 + 单调队列优化DP】
- hihocoder 第120周 后缀数组 + 单调队列