看到后缀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,mid−1],[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(1−x),b(1−x)+hx)
容易证明,两边取等时最大值最小。
这样可以解出x,相应算出答案即可。
时间复杂度 O ( n log n ) O(n\log n) O(nlogn)(log是rmq的复杂度,分治过程是线性的)
#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));
}