省选模拟赛第一场 T1反攻密令(BZOJ4310 跳蚤)(后缀数组统计本质不同的子串数量+二分答案)

 

 

 

 

 

题解

字符串果然还是我的弱项。。。还得继续刷后缀数组和AC自动机

在讨论这道题之前,先来想一下后缀数组有什么用

1、后缀排序

2、O(nlogn)预处理ST表,O(1)求LCP

好像没有什么了吧。。。

其实它的用处还有很多很多:https://blog.csdn.net/c20181220_xiang_m_y/article/details/104017226

这里用到了它的一个用处:把所有本质不同的子串进行排序

怎么做?

把排序好的后缀数组拿出来,从小到大枚举排名 i ,以sa[i]作为左端点,j从sa[i]+height[i]枚举到n,以 j 作为右端点

然后发现这些子串都是本质不同且字典序递增的(其实想想就知道为什么了)

于是我们就可以来对n-(sa[i]+height[i])+1做一个前缀和,就可以用lower_bound来查出一个排名对应的子串啦

 

这道题要求的就是所有划分方案中,对于一个划分方案,求出它划分后的所有串的最大子串的最大串,然后求所有方案的答案的最小串。最小化最大值,可以联想到二分

我们发现对于一个串,如果要成为答案,那么所有字典序大于它的串必须被划分到多个段

当我们二分了一个串,可以考虑倒序贪心,当遇到一个整串的字典序大于二分的答案,我们就把它与它的第一个字符划分成两段

(为什么是倒序,因为固定了一个右端点,左端点l到右段点r中的所有前缀的最大值就是l~r的字符串)

这里还要用到后缀数组的另一个用途:O(1)比较任意两个子串的字典序(详见代码)

代码:٩(๑>◡<๑)۶

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define N 100005
#define LOG 18
#define LL long long
int n,m;char a[N];
int *sa,*rk,*nsa,*nrk,arr[4][N],b[N],h[N];
int f[N][LOG+2],lg[N];
LL sum[N];
void SA()
{
	sa=arr[0];rk=arr[1];nsa=arr[2];nrk=arr[3];
	int i,j,k,mx=0;
	for(i=1;i<=n;i++)b[(int)a[i]]++;
	for(i=1;i<=200;i++)b[i]+=b[i-1];
	for(i=1;i<=n;i++)sa[b[(int)a[i]]--]=i;
	for(i=1;i<=n;i++)rk[sa[i]]=rk[sa[i-1]]+(a[sa[i]]!=a[sa[i-1]]);
	for(k=1;k<=n&&rk[sa[n]]<n;k<<=1){
		for(i=1;i<=n;i++)b[rk[sa[i]]]=i;
		for(i=n;i>=1;i--)if(sa[i]>k)nsa[b[rk[sa[i]-k]]--]=sa[i]-k;// !
		for(i=n-k+1;i<=n;i++)nsa[b[rk[i]]--]=i;// !
		for(i=1;i<=n;i++)nrk[nsa[i]]=nrk[nsa[i-1]]+(rk[nsa[i]]!=rk[nsa[i-1]]||rk[nsa[i]+k]!=rk[nsa[i-1]+k]);
		swap(sa,nsa);swap(rk,nrk);
	}
	for(i=1,k=0;i<=n;i++){
		if(rk[i]==1)h[1]=0;
		else{
			if(k)k--;
			for(j=sa[rk[i]-1];a[i+k]==a[j+k];k++);
			h[rk[i]]=k;
		}
	}
	for(i=1;i<=n;i++){
		f[i][0]=h[i];// !
		lg[i]=lg[i>>1]+1;
		mx=max(mx,(int)a[i]);
	}
	for(j=1;j<=LOG;j++)
		for(i=1;i<=n;i++)if(i+(1<<(j-1))<=n)// !
			f[i][j]=min(f[i][j-1],f[i+(1<<(j-1))][j-1]);
	for(i=1;i<=n;i++){
		if((int)a[sa[i]]==mx)sum[i]+=n-(sa[i]+h[i])+1;
		sum[i]+=sum[i-1];
	}
}
inline int LCP(int x,int y)
{
	if(x==y)return n-x+1;
	x=rk[x];y=rk[y];// !
	if(x>y)swap(x,y);x++;
	int p=lg[y-x+1]-1;
	return min(f[x][p],f[y-(1<<p)+1][p]);
}
inline bool cmp(int l,int r,int x,int y)
{
	int len=min(LCP(l,x),min(r-l+1,y-x+1));
	if(len<r-l+1&&len<y-x+1)return a[l+len]<a[x+len];
	if(len==r-l+1&&len<y-x+1)return 1;
	return 0;
}
void get(int &l,int &r,LL k)
{
	int p=lower_bound(sum+1,sum+n+1,k)-sum;
	l=sa[p];r=sa[p]+h[p]+k-sum[p-1]-1;
}
int check(LL k)
{
	int l,r,i,j,ret=0;
	get(l,r,k);
	for(i=n;i>=1;i=j){
		j=i-1;ret++;
		while(j>=1&&!cmp(l,r,j,i))j--;
	}
	return ret;
}
int main()
{
	freopen("string.in","r",stdin);
	freopen("string.out","w",stdout);
	int i,pl,pr;
	LL l,r,mid;
	scanf("%d%s",&m,a+1);
	n=strlen(a+1);SA();
	l=1;r=sum[n];
	while(l<r){
		mid=(l+r)>>1;
		if(check(mid)>m)
			l=mid+1;
		else
			r=mid;
	}
	get(pl,pr,l);
	for(i=pl;i<=pr;i++)
		printf("%c",a[i]);
}

调了我2h。。。最后发现后缀数组的模板打错了,身败名裂啊啊啊啊。。。。。

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值