【BZOJ2119】股市的预测(后缀数组)
大致题意: 给你一个数列,问你有多少个区间,满足中间有一段长为\(m\)的间隔,且首尾两端走势相同。
前言
被题意杀了。。。研究样例研究了半天。。。
题意转化
考虑走势相同,就是相邻元素两两差值相同。
因此,如果我们存下相邻元素的差值,就变成询问有多少个区间形如\(ABA\)且\(B\)的长度为\(m\)。
神奇做法
对于这个,有一个非常神奇的做法。
我们枚举\(A\)的长度\(x\),然后选取\(1,x+1,2x+1,\lfloor\frac nx\rfloor\times x+1\)这些点作为关键点(根据调和级数,关键点的总数是\(nlogn\)级别的)。
考虑枚举每一个关键点\(i\)作为\(ABA\)中左边的\(A\)中的一点,那么右边的\(A\)的位置就应该在\(j=i+m+x\)。
然后我们求出\(l=LCS(i,j),r=LCP(i,j)\)。其中\(LCP\)是最长公共前缀,相信大家都很熟悉,可以用后缀数组求出;而\(LCS\)就是最长公共后缀,其实就相当于反串的\(LCP\)。
也就是说,以\(i\)为中心,向左\(l\)个位置(包括\(i\)自身),向右\(r\)个位置(同样包括\(i\)自身),这一段区间是可以与\(j\)的对应区间相匹配的。
但需要考虑我们规定了枚举的\(i\)是左边\(A\)中的一点,若\(l>x\)或\(r>x\),那么就会选到其他的关键点。而两个关键点相距\(x\),显然不可能同时存在于一个长为\(x\)的串中,此时就会出现计算重复的情况。
因此,我们需要将\(l,r\)向\(x\)取\(min\)。
而\(i\)的贡献就是\((l+r-1)-(x-1)=l+r-x\),即在共计\(l+r-1\)个字符中选出\(x\)个连续字符的方案数。
然后这道题就做完了。
代码
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 50000
#define LN 20
using namespace std;
int n,m,a[N+5],b[N+5],dc,dv[N+5];long long ans;
class SuffixArray//后缀数组
{
private:
int SA[N+5],rk[N+5],p[N+5],t[N+5],H[N+5],Lg[N+5],Mn[N+5][LN+5];
I void Sort(CI S)
{
RI i;for(i=0;i<=S;++i) t[i]=0;for(i=1;i<=n;++i) ++t[rk[i]];
for(i=1;i<=S;++i) t[i]+=t[i-1];for(i=n;i;--i) SA[t[rk[p[i]]]--]=p[i];
}
I void GetSA(int *s)
{
RI i;for(i=1;i<=n;++i) rk[p[i]=i]=s[i];
RI k,t=0,S=dc;for(Sort(S),k=1;t^n;S=t,k<<=1)
{
for(t=0,i=1;i<=k;++i) p[++t]=n-k+i;
for(i=1;i<=n;++i) SA[i]>k&&(p[++t]=SA[i]-k);
for(Sort(S),i=1;i<=n;++i) p[i]=rk[i];
for(rk[SA[1]]=t=1,i=2;i<=n;++i)
rk[SA[i]]=(p[SA[i-1]]^p[SA[i]]||p[SA[i-1]+k]^p[SA[i]+k])?++t:t;
}
}
I void GetH(int *s)
{
RI i,j,k=0;for(i=1;i<=n;++i) p[SA[i]]=i;
for(i=1;i<=n;++i)
{
if(k&&--k,p[i]==1) continue;j=SA[p[i]-1];
W(i+k<=n&&j+k<=n&&s[i+k]==s[j+k]) ++k;H[p[i]]=k;
}
for(Lg[0]=-1,i=1;i<=n;++i) Mn[i][0]=H[i],Lg[i]=Lg[i>>1]+1;
for(j=1;j<=LN;++j) for(i=1;i+(1<<j)-1<=n;++i)
Mn[i][j]=min(Mn[i][j-1],Mn[i+(1<<j-1)][j-1]);
}
public:
I void Init(int *s) {GetSA(s),GetH(s);}
I int LCP(CI a,CI b)//RMQ求LCP
{
RI x=min(p[a],p[b])+1,y=max(p[a],p[b]),k=Lg[y-x+1];//根据Height的定义,转化为SA
return min(Mn[x][k],Mn[y-(1<<k)+1][k]);
}
}S1,S2;
I void GetAns(CI x)
{
RI i,j,l,r;for(i=1;(j=i+m+x)<=n;i+=x)
l=S2.LCP(n-i+1,n-j+1),l>x&&(l=x),r=S1.LCP(i,j),r>x&&(r=x),//求出l,r,注意向x取min
l+r-1>=x&&(ans+=l+r-x);//计算贡献
}
int main()
{
RI i;for(scanf("%d%d",&n,&m),i=1;i<=n;++i) scanf("%d",a+i);
for(--n,i=1;i<=n;++i) dv[i]=(a[i]-=a[i+1]);sort(dv+1,dv+n+1);
for(dc=unique(dv+1,dv+n+1)-dv-1,i=1;i<=n;++i)//离散化
a[i]=lower_bound(dv+1,dv+dc+1,a[i])-dv,b[n-i+1]=a[i];//b记录反串,用于求LCS
for(S1.Init(a),S2.Init(b),i=1;m+2*i<=n;++i) GetAns(i);//枚举A串长度
return printf("%lld\n",ans),0;
}
待到再迷茫时回头望,所有脚印会发出光芒