把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【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;
}
posted @ 2020-05-19 12:43  TheLostWeak  阅读(125)  评论(0编辑  收藏  举报