【后缀数组】[TJOI2015]弦论

本文探讨了使用SA(Suffix Array)算法解决子串排名问题的两种情况:一是将不同位置的相同子串视为一个,二是视为多个。通过构建后缀数组、计算最长公共前缀数组,并利用ST表优化查询过程,提出了高效的解决方案。文章详细解析了二分查找在子串排名确定中的应用,以及如何计算特定排名的子串。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目

不同位置相同子串算一个,以及不同位置相同子串算多个,求第K小的子串

题解

SA好题啊! 考场写挂。。。
前一个问非常简单
每个后缀有 n − s a i − h i + 1 n-sa_i-h_i+1 nsaihi+1个子串贡献


后一个问思考二分前一问的排名
令前一问排名为 m i d mid mid的字符串的起始位在第 p o s pos pos名后缀处
算验证比 m i d mid mid小的个数是否大于 k k k
计算比 m i d mid mid小的个数:
∑ i = 1 p o s − 1 − s a i + 1 + ∑ i = p o s n m i n ( l c p ( p o s , i ) , w ) \sum_{i=1}^{pos-1}-sa_i+1+\sum_{i=pos}^{n}min(lcp(pos,i),w) i=1pos1sai+1+i=posnmin(lcp(pos,i),w)
l c p lcp lcp用ST表O(1)即可

#include<bits/stdc++.h>
using namespace std;
const int N=5e5+10;
int n,m,op;
char s[N];
int rk[N],sa[N],tp[N],t[N];
void Qsort()
{
	for(int i=1;i<=m;i++)t[i]=0;
	for(int i=1;i<=n;i++)t[rk[i]]++;
	for(int i=1;i<=m;i++)t[i]+=t[i-1];
	for(int i=n;i>=1;i--)sa[t[rk[tp[i]]]--]=tp[i];
}
void SA()
{
	for(int i=1;i<=n;i++)rk[i]=s[i]-'a'+1,tp[i]=i;m=30;Qsort();
	for(int w=1,p;w<=n;m=p,w<<=1)
	{
		p=0;
		for(int i=n-w+1;i<=n;i++)tp[++p]=i;
		for(int i=1;i<=n;i++)if(sa[i]>w)tp[++p]=sa[i]-w;
		Qsort();swap(tp,rk);p=rk[sa[1]]=1;
		for(int i=2;i<=n;i++)rk[sa[i]]=(tp[sa[i]]==tp[sa[i-1]]&&tp[sa[i]+w]==tp[sa[i-1]+w])?p:++p;
		if(p==n)return ;
	}
}
int h[N];
void get_h()
{
	int k=0;
	for(int i=1;i<=n;i++)
	{
		if(rk[i]==1)continue;
		int j=sa[rk[i]-1];if(k)k--;
		while(i+k<=n&&j+k<=n&&s[i+k]==s[j+k])k++;
		h[rk[i]]=k;
	}
}
int bz[N][20];
void ST()
{
	for(int i=1;i<=n;i++)bz[i][0]=h[i];
	for(int j=1;j<=20;j++)
		for(int i=1;i<=n-(1<<j)+1;i++)
			bz[i][j]=min(bz[i][j-1],bz[i+(1<<(j-1))][j-1]);
}
int minn(int l,int r)
{
	if(l!=r)l++;
	else return n-sa[l]+1;
	int w=log2(r-l+1);
	return min(bz[l][w],bz[r-(1<<w)+1][w]);
}
//--
void get(int k,int &st,int &len)
{
	st=0;
	for(int i=1;i<=n;i++)
	{
		int w=n-sa[i]-h[i]+1;
		if(k<=w){st=i;len=k;return ;}
		k-=w;
	}
}
int k;
int st,len;
bool check(int mid)
{
	get(mid,st,len);
	int tmp=0;
	for(int i=1;i<st;i++)tmp+=n-sa[i]+1;
	for(int i=st;i<=n;i++)tmp+=min(len,minn(st,i));
	return tmp>=k;
}
int main()
{
	scanf("%s%d%d",s+1,&op,&k);
	n=strlen(s+1);
	SA();get_h();ST();
	if(op==0)
	{
		get(k,st,len);
		if(st)for(int i=sa[st];i<sa[st]+len+h[st];i++)printf("%c",s[i]);
		else  printf("-1");
	}
	else
	{
		if(k>1ll*n*(n+1)/2){printf("-1");return 0;}
		int l=0,r=k+1,ans=-1;
		while(l<=r)
		{
			int mid=(l+r)>>1;
			if(check(mid))r=mid-1,ans=mid;
			else		  l=mid+1;
		}
		get(ans,st,len);
		for(int i=sa[st];i<sa[st]+len;i++)printf("%c",s[i]);
	}
}

其实此题原本是SAM板题,但作者有一点心理阴影,就等到什么时候有空再补SAM做法 咕掉吧 Q w Q QwQ QwQ

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值