[NOI2016]优秀的拆分&&BZOJ2119股市的预测
[NOI2016]优秀的拆分
https://www.lydsy.com/JudgeOnline/problem.php?id=4650
题解
如果我们能够统计出一个数组a,一个数组b,a[i]表示以i为结尾的AA串个数,b[i]表示以i开头的AA串个数,我们就可以O(n)的统计答案了。
我们开看这么一个算法。
先枚举A的长度len,然后按照A的长度把序列分成很多段,每段有一个代表点即这一段的第一个点。
然后枚举所有代表点l,再枚举r=l+len,求一下(1,l)和(1,r)的最长公共后缀,(l,n)和(r,n)的最长公共前缀。
就像这样,但如果是上述情况的话,是无法拼成AA串的,因为蓝色和橙色并没有完全覆盖中间的红色段。
如果是这样的话就可以了,有多少个呢?蓝色和橙色组成的大的线段的左右端点就是第一条线段的左端点和第二条线段的右端点,这条线段可以在中间任意滑动。
然后就可以差分统计答案了。
这种算法相当于利用两个A的开头不会在同一个块内出现,只会在相邻两个块出现的性质做的。
注意,在求公共前后缀时要和和len取min,否则就会算多。
代码
#include<iostream> #include<cstdio> #include<cmath> #include<algorithm> #include<cstring> #define N 30002 using namespace std; int n,m,y[N],tong[N],T; long long tag1[N],tag2[N],ans; inline int rd(){ int x=0;char c=getchar();bool f=0; while(!isdigit(c)){if(c=='-')f=1;c=getchar();} while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=getchar();} return f?-x:x; } struct suffixarray{ int rnk[N],sa[N],height[N],p[18][N]; char s[N]; inline void qsort(){ for(int i=1;i<=n;++i)tong[rnk[i]]++; for(int i=1;i<=m;++i)tong[i]+=tong[i-1]; for(int i=n;i>=1;--i)sa[tong[rnk[y[i]]]--]=y[i]; for(int i=0;i<=m;++i)tong[i]=0; } inline void SA(){ m=200; for(int i=1;i<=n;++i)rnk[i]=s[i],y[i]=i; qsort(); for(int w=1,p=0;p<n;m=p,w<<=1){ p=0; for(int i=n-w+1;i<=n;++i)y[++p]=i; for(int i=1;i<=n;++i)if(sa[i]>w)y[++p]=sa[i]-w; qsort();swap(rnk,y); rnk[sa[1]]=p=1; for(int i=2;i<=n;++i) rnk[sa[i]]=((y[sa[i]]==y[sa[i-1]])&&(y[sa[i]+w]==y[sa[i-1]+w]))?p:++p; } for(int i=1;i<=n;++i){ if(rnk[i]==1)continue; int j=max(0,height[rnk[i-1]]-1); while(s[i+j]==s[sa[rnk[i]-1]+j])++j; height[rnk[i]]=j; p[0][rnk[i]]=j; } for(int i=1;(1<<i)<=n;++i) for(int j=1;j+(1<<i)-1<=n;++j) p[i][j]=min(p[i-1][j],p[i-1][j+(1<<i-1)]); } inline int query(int l,int r){ if(l>r)swap(l,r);l++; if(l>r)return 2e9; int lo=log2(r-l+1); return min(p[lo][l],p[lo][r-(1<<lo)+1]); } inline void clear(){ // memset(p,0,sizeof(p)); memset(height,0,sizeof(height)); memset(s,0,sizeof(s)); memset(rnk,0,sizeof(rnk)); memset(sa,0,sizeof(sa)); } }z,f; inline void init(){ memset(y,0,sizeof(y)); ans=0; memset(tag1,0,sizeof(tag1));memset(tag2,0,sizeof(tag2)); } int main(){ T=rd(); while(T--){ z.clear();f.clear();init(); scanf("%s",z.s+1);n=strlen(z.s+1); for(int i=1;i<=n;++i)f.s[i]=z.s[n-i+1];f.s[n+1]=z.s[n+1]=0; f.SA();z.SA(); for(int i=1;i<=n/2;++i) for(int j=1;j+i<=n;j+=i){ int l=j,r=j+i; int lcp=min(i,z.query(z.rnk[l],z.rnk[r])),lcs=min(i,f.query(f.rnk[n-l+1],f.rnk[n-r+1])); if(lcp+lcs>=i+1){ tag1[l-lcs+(i<<1)]++;tag1[r+lcp]--; tag2[l-lcs+1]++;tag2[r+lcp-(i<<1)+1]--; } //cout<<l<<" "<<r<<" "<<lcp<<" "<<lcs<<endl; } for(int i=1;i<=n;++i)tag1[i]+=tag1[i-1],tag2[i]+=tag2[i-1]; for(int i=1;i<=n;++i)ans+=tag1[i-1]*tag2[i]; cout<<ans<<endl; } return 0; }
股市的预测
墨墨的妈妈热爱炒股,她要求墨墨为她编写一个软件,预测某只股票未来的走势。股票折线图是研究股票的必备工
具,它通过一张时间与股票的价位的函数图像清晰地展示了股票的走势情况。经过长时间的观测,墨墨发现很多股
票都有如下的规律:之前的走势很可能在短时间内重现!如图可以看到这只股票A部分的股价和C部分的股价的走势
如出一辙。通过这个观测,墨墨认为他可能找到了一个预测股票未来走势的方法。进一步的研究可是难住了墨墨,
他本想试图统计B部分的长度与发生这种情况的概率关系,不过由于数据量过于庞大,依赖人脑的力量难以完成,
于是墨墨找到了善于编程的你,请你帮他找一找给定重现的间隔(B部分的长度),有多少个时间段满足首尾部分
的走势完全相同呢?当然,首尾部分的长度不能为零。
题解
我们把序列差分之后,相当于是求形如ABA的串,这和上一题就差不多了。
每次取得l=i,r=i+m+len。然后就和上一题一样了。
每次新增加的答案为max(0,公共前缀+公共后缀-len)
代码
#include<iostream> #include<cstdio> #include<cmath> #include<algorithm> #define N 50002 using namespace std; int n,m,y[N],ans,tong[N],M,b[N],c[N]; inline int rd(){ int x=0;char c=getchar();bool f=0; while(!isdigit(c)){if(c=='-')f=1;c=getchar();} while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=getchar();} return f?-x:x; } struct suffixarray{ int rnk[N],sa[N],a[N],height[N],p[18][N]; inline void qsort(){ for(int i=0;i<=m;++i)tong[i]=0; for(int i=1;i<=n;++i)tong[rnk[i]]++; for(int i=1;i<=m;++i)tong[i]+=tong[i-1]; for(int i=n;i>=1;--i)sa[tong[rnk[y[i]]]--]=y[i]; } inline void SA(){ m=200; for(int i=1;i<=n;++i)rnk[i]=a[i],y[i]=i; qsort(); for(int w=1,p=0;p<n;m=p,w<<=1){ p=0; for(int i=n-w+1;i<=n;++i)y[++p]=i; for(int i=1;i<=n;++i)if(sa[i]>w)y[++p]=sa[i]-w; qsort();swap(rnk,y); rnk[sa[1]]=p=1; for(int i=2;i<=n;++i) rnk[sa[i]]=((y[sa[i]]==y[sa[i-1]])&&(y[sa[i]+w]==y[sa[i-1]+w]))?p:++p; } for(int i=1;i<=n;++i){ if(rnk[i]==1)continue; int j=max(0,height[rnk[i-1]]-1); while(a[i+j]==a[sa[rnk[i]-1]+j])++j; height[rnk[i]]=j; p[0][rnk[i]]=j; } for(int i=1;(1<<i)<=n;++i) for(int j=1;j+(1<<i)-1<=n;++j) p[i][j]=min(p[i-1][j],p[i-1][j+(1<<i-1)]); } inline int query(int l,int r){ if(l>r)swap(l,r);l++; if(l>r)return 2e9; int lo=log2(r-l+1); return min(p[lo][l],p[lo][r-(1<<lo)+1]); } }z,f; int main(){ n=rd();M=rd(); for(int i=1;i<=n;++i)b[i]=rd(); for(int i=n-1;i>=1;--i)z.a[i]=b[i+1]-b[i],c[i]=z.a[i];n--; sort(c+1,c+n+1);int tt=unique(c+1,c+n+1)-c-1; for(int i=1;i<=n;++i)z.a[i]=lower_bound(c+1,c+tt+1,z.a[i])-c; for(int i=1;i<=n;++i)f.a[n-i+1]=z.a[i]; z.SA();f.SA(); for(int i=1;i<=(n-M)/2;++i){ for(int j=1;j<=n&&j+i+M<=n;j+=i){ int l=j,r=i+j+M; int lcp=min(i,z.query(z.rnk[l],z.rnk[r])),lcs=min(i,f.query(f.rnk[n-l+1],f.rnk[n-r+1])); int x=lcs+lcp-i; if(x>0)ans+=x; } } cout<<ans; return 0; }