[JZOJ6042]【NOI2019五校联考2019.3.5】Second【SA】【分治】

Description

[JZOJ6042]【NOI2019五校联考2019.3.5】Second【SA】【分治】_第1张图片
[JZOJ6042]【NOI2019五校联考2019.3.5】Second【SA】【分治】_第2张图片

Solution

看到后缀lcp,自然而然想到SA的height数组。
然后在height上贪心一波?

由于后缀间lcp是区间height,自然而然想到取出height的最小值,然后将height序列分成两段。

有一个很重要的结论是对于某一段,最优答案与分配的k值之和是成正比的。
也就是说我们并不需要知道具体某一段分配了多少,先完全可以假定它总共分配的k值和1,如果它最后实际上分配的k值和为1/2,那么之前算出的k值和为1的最优解乘上1/2就是新的答案。

为什么?考虑k值的成倍变化,最优解一定是满足一定的分配比例的,k值和乘上某个数一定会将每一个分配的k都乘上这个数,答案也会乘上这个数。

有了这个结论,我们就可以分治了。

设当前做到区间 [ l , r ] [l,r] [l,r]
取出height的最小值位置为 m i d , h = h e i g h t [ m i d ] mid,h=height[mid] mid,h=height[mid],它将区间分成 [ l , m i d − 1 ] , [ m i d , r ] [l,mid-1],[mid,r] [l,mid1],[mid,r]两段
递归处理,假设我们算出了左端分配和为1的k以后答案为a,右端答案为b
现在我们重新安排左右的比例,设左端实际分配的k值和为x,假设总共仍然分配了1

那么答案就是 m a x ( a x + h ( 1 − x ) , b ( 1 − x ) + h x ) max(ax+h(1-x),b(1-x)+hx) max(ax+h(1x),b(1x)+hx)
容易证明,两边取等时最大值最小。
这样可以解出x,相应算出答案即可。
时间复杂度 O ( n log ⁡ n ) O(n\log n) O(nlogn)(log是rmq的复杂度,分治过程是线性的)

Code

#include 
#define fo(i,a,b) for(int i=a;i<=b;++i)
#define fod(i,a,b) for(int i=a;i>=b;--i)
#define N 1000005
using namespace std;
int n,height[N],SA[N],s2[N],rank[N],r1[N],ct[N],rmq[20][N],l2[N];
char ch[N];
void make()
{
	memset(ct,0,sizeof(ct));
	fo(i,1,n) ct[rank[i]=ch[i]-'a'+1]++;
	fo(i,1,26) ct[i]+=ct[i-1];
	fod(i,n,1) SA[ct[rank[i]]--]=i;
	for(int j=1,k=0,mx=26;k<n;j<<=1,mx=k)
	{
		int p=0;
		fo(i,n-j+1,n) s2[++p]=i;
		fo(i,1,n) if(SA[i]>j) s2[++p]=SA[i]-j;
		memset(ct,0,sizeof(ct));
		fo(i,1,n) ct[rank[s2[i]]]++;
		fo(i,1,mx) ct[i]+=ct[i-1];
		fod(i,n,1) SA[ct[rank[s2[i]]]--]=s2[i];
		r1[SA[1]]=k=1;
		fo(i,2,n) r1[SA[i]]=(rank[SA[i-1]]==rank[SA[i]]&&rank[SA[i-1]+j]==rank[SA[i]+j])?k:++k;
		fo(i,1,n) rank[i]=r1[i];
	}
}
void getheight()
{
	height[1]=0;
	for(int i=1,j=0;i<=n;++i)
	{
		if(rank[i]==1) continue;
		j=max(height[rank[i-1]]-1,0);
		while(ch[i+j]==ch[SA[rank[i]-1]+j]) j++;
		height[rank[i]]=j;
	}
}
int gmin(int l,int r)
{
	int m=l2[r-l+1];
	return (height[rmq[m][l]]<=height[rmq[m][r-(1<<m)+1]])?rmq[m][l]:rmq[m][r-(1<<m)+1];
}
double doit(int l,int r)
{
	if(l==r) return n-SA[l]+1;
	int p=gmin(l+1,r);
	double a=doit(l,p-1),b=doit(p,r),h=height[p],x=(b-h)/(a+b-2*h);
	return a*x+h*(1-x);
}
int main()
{
	scanf("%s",ch+1);
	n=strlen(ch+1);	
	make();
	getheight();
	fo(i,1,19) l2[1<<i]=i;
	fo(i,2,n) if(!l2[i]) l2[i]=l2[i-1];
	fo(i,1,n) rmq[0][i]=i;
	fo(j,1,19)
	{
		fo(i,1,n)
		{
			rmq[j][i]=(rmq[j-1][i]+(1<<j-1)>n||height[rmq[j-1][i]]<=height[rmq[j-1][i+(1<<j-1)]])?rmq[j-1][i]:rmq[j-1][i+(1<<j-1)];
		}
	}
	printf("%.6lf\n",doit(1,n));
}

你可能感兴趣的:(题解,————SA,————分治,---字符串)