题目:[TJOI2019]甲苯先生和大中锋的字符串
大中锋有一个长度为 n 的字符串,他只知道其中的一个子串是祖上传下来的宝藏的密码。但是由于字符串很长,大中锋很难将这些子串一一尝试。
这天大中锋找到甲苯先生算命,但是甲苯先生说:“天机不可泄漏”。
在大中锋的苦苦哀求下,甲苯先生告诉大中锋:“密码是在字符串中恰好出现了 kk 次的子串”。
但是大中锋不知道该怎么做,在大中锋再三的恳求下,甲苯先生看其真诚,又告诉他:“在恰好出现了 k 次的子串中,你去按照字串的长度分类,密码就在数量最多的那一类里”。
大中锋为了尝试这个密码,想让你帮忙找出子串长度出现次数最多的长度数(如果有多个输出最长长度)。
题解(后缀数组):
后缀数组好题,题解说是大水题,但我看不出来。
题还是做得太少,对后缀数组的理解也太浅。
我们用一个长度为k的滑块去滑字符串。
字符串的先后顺序是按后缀排序来的。
我们维护了区间的最小height值,记为x,
这是什么?
如果x为0,说明这样的前缀失败了,出现次数不到k
如果x不为0,说明此时长度为x的前缀至少有k个
但我们要求恰好为k的个数
那怎么办呢?
有了,我们判一下,h[i-k+1]和h[i+1]
如果这两个值中有值大于等于x,这说明此时长度为x的前缀超过了k个
否则这样长度为x的前缀恰好等于k个。
考虑前缀中能分出多少的前缀恰好等于k,最小的就是max(h[i-k+1],h[i+1])+1,最大的是x
计算答案就差分一下即可,维护最大值 .
代码:
#include
using namespace std;
#define next Next
const int N=4e5+5;
int n,m,k,q[N],cf[N],sa[N],height[N],x[N],y[N],c[N],rk[N];
char a[N];
/*char buf[1<<21],*p1=buf,*p2=buf;
inline int gc(){return p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++;}*/
#define gc getchar
inline int read()
{
int ret=0,f=0;char c=gc();
while(!isdigit(c)){if(c=='-')f=1;c=gc();}
while(isdigit(c)){ret=ret*10+c-48;c=gc();}
if(f)return -ret;return ret;
}
void SA(int n,int m)
{
int p;
for(int i=0;i<=n;i++)
{
sa[i]=rk[i]=x[i]=y[i]=0;
}
for(int i=1;i<=m;i++)c[i]=0;
for(int i=1;i<=n;i++)c[x[i]=a[i]]++;
for(int i=1;i<=m;i++)c[i]+=c[i-1];
for(int i=n;i;i--)sa[c[x[i]]--]=i;
for(int i=1;i;i=i*2)
{
p=0;
for(int j=n-i+1;j<=n;j++)y[++p]=j;
for(int j=1;j<=n;j++)
if(sa[j]>i)y[++p]=sa[j]-i;
for(int j=1;j<=m;j++)c[j]=0;
for(int j=1;j<=n;j++)c[x[y[j]]]++;
for(int j=1;j<=m;j++)c[j]+=c[j-1];
for(int j=n;j;j--)sa[c[x[y[j]]]--]=y[j];
swap(x,y);
x[sa[1]]=1;
p=2;
for(int j=2;j<=n;j++)
{
if(y[sa[j]]==y[sa[j-1]]&&y[sa[j]+i]==y[sa[j-1]+i])x[sa[j]]=p-1;
else x[sa[j]]=p++;
}
if(p>n)return;
m=p;
}
}
void Height()
{
int k=0;
for(int i=1;i<=n;i++)rk[sa[i]]=i;
for(int i=1;i<=n;i++)
{
if(rk[i]==1)continue;
if(k)k--;
int j=sa[rk[i]-1];
while(i+k<=n&&j+k<=n&&a[i+k]==a[j+k])k++;
height[rk[i]]=k;
}
}
signed main()
{
int T=read();
while(T--)
{
memset(cf,0,sizeof(cf));
scanf("%s",a+1);
n=strlen(a+1);
k=read();
height[n+1]=0;
SA(n,122);
Height();
int l=1,r=0;
for(int i=1;i<=k;i++)
{
while(l=height[i])r--;
q[++r]=i;
}
for(int i=k;i<=n;i++)
{
if(i-q[l]+1>=k)l++;
while(l=height[i])r--;
q[++r]=i;
int R;
if(k==1)R=n-sa[i+k-1]+1;
else R=height[q[l]];
int L=max(height[i-k+1],height[i+1]);
if(L<R)
{
cf[L+1]++;
cf[R+1]--;
}
}
int sum=cf[0],ma=1,ans=-1;
for(int i=1;i<=n;i++)
{
sum+=cf[i];
if(sum>=ma)
{
ma=sum;
ans=i;
}
}
printf("%d\n",ans);
}
return 0;
}
/*
后缀数组好题,题解说是大水题,但我看不出来。
题还是做得太少,对后缀数组的理解也太浅。
我们用一个长度为k的滑块去滑字符串。
字符串的先后顺序是按后缀排序来的。
我们维护了区间的最小height值,记为x,
这是什么?
如果x为0,说明这样的前缀失败了,出现次数不到k
如果x不为0,说明此时长度为x的前缀至少有k个
但我们要求恰好为k的个数
那怎么办呢?
有了,我们判一下,h[i-k+1]和h[i+1]
如果这两个值中有值大于等于x,这说明此时长度为x的前缀超过了k个
否则这样长度为x的前缀恰好等于k个。
考虑前缀中能分出多少的前缀恰好等于k,最小的就是max(h[i-k+1],h[i+1])+1,最大的是x
计算答案就差分一下即可,维护最大值 .
*/ 
京公网安备 11010502036488号