HDU 6194 String String String 后缀数组 正好出现K次的子串个数 CSU1632 至少出现2次的子串个数
2017-09-27 00:04
405 查看
求正好出现K次的子串个数。
对于k≥2 的时候 ,维护一个大小为k-1的区间,LCP(l,r)就是该区间内出现K次的子串个数,因为有些子串可能会在与这个区间的相邻的两端出现,所以要把他们减掉,即贡献是LCP(l,r)−max(height[l−1],height[r+1])
对于 k=1 的时候,要求的就是只出现一次的子串个数,联想到后缀数组可以求不同的子串个数,方法是n−sa[i]−height[i] ,求不同串个数的时候只减去了左边,所以这个串第一次出现一定会被记录,之后再出现就会被减掉了,要求只出现一次的,就是n−sa[i]−max(height[i],height[i+1]) ,简单的理解就是只要在相邻rank中出现过的都不算。
最后模板打错,WA到怀疑人生。。
同样的,出现两次,我们先维护一个长度为1的区间,如果是求正好两次,那么就是height[i]−max(height[i−1],height[i+1]),如果是求出现至少两次,那么就是height[i]−height[i−1]。注意若小于0则不计入答案。
从这两题里我们可以看出,如果只减去前面的,那么求的就是至少K次。如果两边都减,那么就是正好K次。
对于k≥2 的时候 ,维护一个大小为k-1的区间,LCP(l,r)就是该区间内出现K次的子串个数,因为有些子串可能会在与这个区间的相邻的两端出现,所以要把他们减掉,即贡献是LCP(l,r)−max(height[l−1],height[r+1])
对于 k=1 的时候,要求的就是只出现一次的子串个数,联想到后缀数组可以求不同的子串个数,方法是n−sa[i]−height[i] ,求不同串个数的时候只减去了左边,所以这个串第一次出现一定会被记录,之后再出现就会被减掉了,要求只出现一次的,就是n−sa[i]−max(height[i],height[i+1]) ,简单的理解就是只要在相邻rank中出现过的都不算。
最后模板打错,WA到怀疑人生。。
#include <iostream> #include <bits/stdc++.h> using namespace std; typedef long long LL; const int MAXN = 1e5+1000; char s[MAXN]; int num[MAXN],wa[MAXN],wb[MAXN],wv[MAXN],wd[MAXN],rk[MAXN],sa[MAXN],height[MAXN],n,minl[MAXN][20],k; int cmp(int *r,int a,int b,int l) { return r[a]==r[b] && r[a+l]==r[b+l]; } void SA(int *r,int n) { int *x=wa,*y=wb,m=0; for (int i=0;i<n;i++) m=max(m,r[i]+1); for (int i=0;i<m;i++) wd[i]=0; for (int i=0;i<n;i++) ++wd[x[i]=r[i]]; for (int i=1;i<m;i++) wd[i]+=wd[i-1]; for (int i=n-1;i>=0;i--) sa[--wd[x[i]]]=i; int p=1; for (int j=1;p<n;j<<=1,m=p) { p=0; for(int i=n-j; i<n; ++i) y[p++]=i; for(int i=0; i<n; ++i) if(sa[i]>=j) y[p++]=sa[i]-j; for(int i=0; i<n; ++i) wv[i]=x[y[i]]; for (int i=0;i<m;i++) wd[i]=0; for (int i=0;i<n;i++) ++wd[wv[i]]; for (int i=1;i<m;i++) wd[i]+=wd[i-1]; for (int i=n-1;i>=0;i--) sa[--wd[wv[i]]]=y[i]; swap(x,y); x[sa[0]]=0;p=1; for (int i=1;i<n;i++) x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++; } for (int i=1;i<n;i++) rk[sa[i]]=i; int k=0; for (int i=0;i<n-1;height[rk[i++]]=k) { if (k)--k; for (int j=sa[rk[i]-1];r[i+k]==r[j+k];++k); } } void initRMQ() { int l=int(log(n)/log(2.0)); for (int i=1;i<=n;i++) minl[i][0]=height[i]; for (int j=1;j<=l;j++) for (int i=1;i+(1<<(j-1)) <=n ;i++) minl[i][j] = min(minl[i][j-1] , minl[i+(1<<(j-1))][j-1]); } int askRMQ(int l,int r) { int k=int(log(r-l+1)/log(2)); return min(minl[l][k] , minl[r-(1<<k)+1][k]); } void sov() { LL ans=0; if (k>=2) { int l=2,r=k; while (r<=n) { LL tmp=askRMQ(l,r) - max(height[l-1],height[r+1]); if (tmp > 0 ) ans+=tmp; l++;r++; } } else { for (int i=1;i<=n;i++) ans += n-sa[i]-max(height[i],height[i+1]); } printf("%lld\n",ans); } int main() { int t; scanf("%d",&t); while (t--) { memset(sa,0,sizeof sa); memset(rk,0,sizeof rk); memset(height,0,sizeof height); scanf("%d%s",&k,s); int len=strlen(s); n=len; for (int i=0;i<n;i++) num[i] =s[i]; num =0; SA(num,n+1); initRMQ(); sov(); } return 0; }
CSU1632
求至少出现两次的子串个数。同样的,出现两次,我们先维护一个长度为1的区间,如果是求正好两次,那么就是height[i]−max(height[i−1],height[i+1]),如果是求出现至少两次,那么就是height[i]−height[i−1]。注意若小于0则不计入答案。
从这两题里我们可以看出,如果只减去前面的,那么求的就是至少K次。如果两边都减,那么就是正好K次。
相关文章推荐
- 2017沈阳网络赛 1001 HDU 6194 string string string(后缀自动机 出现k次的子串个数)
- SPOJ 220后缀数组:求每个字符串至少出现两次且不重叠的最长子串
- HDU 4641 至少出现K次本质不同子串数:后缀自动机
- poj 3294 后缀数组求至少在k个串中出现的公共子串
- PKU 3261(求最长的至少出现k次的子串,后缀数组+二分枚举)
- POJ题目3229 Facer’s string(后缀数组求a串长度为k子串有几个出现在b串)
- poj 3882 后缀数组 求一个串至少出现k次的最长重复子串的长度
- spoj 220 Relevant Phrases of Annihilation (后缀数组 每个串中都至少出现两次的不重叠最长子串)
- HDU 6194 string string string 后缀数组 + RMQ(线段树)
- poj3261(后缀数组求至少出现k次的最长子串可重叠)
- HDU 6194 string string string [后缀数组]
- poj 3261 Milk Patterns (后缀数组 至少出现k次的可重叠最长重复子串)
- HDU 6194 string string string :后缀数组+单调队列 | 后缀自动机
- HDU 4416 Good Article Good sentence(后缀数组 求在只在某串中出现的子串数目)
- string string string hdu 6194 (后缀数组做法)
- HDU 6194 string string string【后缀数组】
- spoj220 每个字符串至少出现两次且不重叠的最长子串(后缀数组)
- POJ 2774 Long Long Message+Hdu 1403 Longest Common Substring (后缀数组 最长公共子串)
- Hdu 4080 & Poj 3882 Stammering Aliens (后缀数组 可重叠k次最长重复子串)
- poj 3261 Milk Patterns 最长的出现最少k次的重复(可重叠)子串 后缀数组