您的位置:首页 > 其它

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到怀疑人生。。

#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次。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐