【BZOJ2119】股市的预测(后缀数组)

点此看题面

大致题意: 给你一个数列,问你有多少个区间,满足中间有一段长为(m)的间隔,且首尾两端走势相同。

前言

被题意杀了。。。研究样例研究了半天。。。

题意转化

考虑走势相同,就是相邻元素两两差值相同。

因此,如果我们存下相邻元素的差值,就变成询问有多少个区间形如(ABA)(B)的长度为(m)

神奇做法

对于这个,有一个非常神奇的做法。

我们枚举(A)的长度(x),然后选取(1,x+1,2x+1,lfloorfrac nx floor imes 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
",ans),0;
}
原文地址:https://www.cnblogs.com/chenxiaoran666/p/BZOJ2119.html