[BZOJ]2119: 股市的预测 分治+主席树+后缀数组

12 篇文章 0 订阅
3 篇文章 0 订阅

Description

墨墨的妈妈热爱炒股,她要求墨墨为她编写一个软件,预测某只股票未来的走势。股票折线图是研究股票的必备工具,它通过一张时间与股票的价位的函数图像清晰地展示了股票的走势情况。经过长时间的观测,墨墨发现很多股票都有如下的规律:之前的走势很可能在短时间内重现!如图可以看到这只股票A部分的股价和C部分的股价的走势如出一辙。通过这个观测,墨墨认为他可能找到了一个预测股票未来走势的方法。进一步的研究可是难住了墨墨,他本想试图统计B部分的长度与发生这种情况的概率关系,不过由于数据量过于庞大,依赖人脑的力量难以完成,于是墨墨找到了善于编程的你,请你帮他找一找给定重现的间隔(B部分的长度),有多少个时间段满足首尾部分的走势完全相同呢?当然,首尾部分的长度不能为零。

Solution

这题网上主流的做法是利用NOI2016优秀的拆分那个套路,这里介绍一种不同的做法。
搞出后缀数组,然后推一下式子,得到这个东西:

int x=max(sa[i],sa[j]),y=min(sa[i],sa[j]);
if(x-y>L&&x-L<=y+query_mn(j+1,i))ans++;

中间的 q u e r y _ m n ( j + 1 , i ) query\_mn(j+1,i) query_mn(j+1,i)算的是 s a [ i ] 、 s a [ j ] sa[i]、sa[j] sa[i]sa[j]的最长公共前缀。
然后就要数这样的 ( i , j ) (i,j) (i,j)个数。
数这种东西一般可以考虑分治,但是由于中间有个最小值,比较难搞,考虑不从中间切开,而是从最小值切开,这样问题就转化为求区间内某范围数的个数。
但是这样有个问题,这样分治层数会很多,但是又可以发现,其实每一层,复杂度都是较短一段长度乘上 log ⁡ \log log,所以复杂度可以看做是启发式分裂的复杂度,为 O ( n log ⁡ 2 n ) O(n\log^2n) O(nlog2n)

Code

#include<bits/stdc++.h>
using namespace std;
#define LL long long
#define pa pair<int,int>
const int Maxn=50010;
const int inf=2147483647;
int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9')x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
    return x*f;
}
int n,m=0,L,a[Maxn],s[Maxn],Log[Maxn];
struct Node{int x,id;}A[Maxn];
bool cmp(Node a,Node b){return a.x<b.x;}
int sa[Maxn],rk[Maxn],t[Maxn],wr[Maxn],tmp[Maxn<<1],b[Maxn],height[Maxn],h[Maxn];
/*b[i]排名为i的第二关键字的第一关键字的后缀开始位置*/
void get_sa()
{
    for(int i=0;i<=m;i++)t[i]=0;
    for(int i=1;i<=n;i++)t[rk[i]=s[i]]++;
    for(int i=1;i<=m;i++)t[i]+=t[i-1];
    for(int i=n;i;i--)sa[t[s[i]]--]=i;
    int len=1,mx=0,k;
    while(mx<n)
    {
        k=0;
        for(int i=n-len+1;i<=n;i++)b[++k]=i;
        for(int i=1;i<=n;i++)if(sa[i]>len)b[++k]=sa[i]-len;
        for(int i=0;i<=m;i++)t[i]=0;
        for(int i=1;i<=n;i++)t[wr[i]=rk[b[i]]]++;
        for(int i=1;i<=m;i++)t[i]+=t[i-1];
        for(int i=n;i;i--)sa[t[wr[i]]--]=b[i];
        int cnt=1;
        for(int i=1;i<=n;i++)tmp[i]=rk[i];
        rk[sa[1]]=1;
        for(int i=2;i<=n;i++)
        {
            if(tmp[sa[i]]!=tmp[sa[i-1]]||tmp[sa[i]+len]!=tmp[sa[i-1]+len])cnt++;
            rk[sa[i]]=cnt;
        }
        mx=m=cnt;len<<=1;
    }
}
/*设h[i]=height[rank[i]],有性质:h[i]>=h[i-1]-1,利用这个求height数组*/
int Mn[Maxn][16];
int _min(int x,int y){return(height[x]<=height[y])?x:y;}
int query_mn(int l,int r)
{
    int t=Log[r-l+1];
    return _min(Mn[l][t],Mn[r-(1<<t)+1][t]);
}
void get_height()
{
    for(int i=1;i<=n;i++)
    {
        int j=sa[rk[i]-1],k=height[rk[i-1]];
        if(k)k--;
        while(s[i+k]==s[j+k])k++;
        height[rk[i]]=k;
    }
    for(int i=1;i<=n;i++)Mn[i][0]=i;
    for(int j=1;(1<<j)<=n;j++)
    for(int i=1;i+(1<<j)-1<=n;i++)
    Mn[i][j]=_min(Mn[i][j-1],Mn[i+(1<<(j-1))][j-1]);
}
int root[Maxn],lc[Maxn*20],rc[Maxn*20],c[Maxn*20],tot=0;
void insert(int &u,int l,int r,int p)
{
    if(!u)u=++tot;
    c[u]++;
    if(l==r)return; 
    int mid=l+r>>1;
    if(p<=mid)insert(lc[u],l,mid,p);
    else insert(rc[u],mid+1,r,p);
}
void merge(int &u1,int u2)
{
    if(!u1){u1=u2;return;}
    if(!u2)return;
    c[u1]+=c[u2];
    merge(lc[u1],lc[u2]),merge(rc[u1],rc[u2]);
}
int query(int L,int R,int l,int r,int fl,int fr)
{
    if(!R||fl>fr)return 0;
    if(l==fl&&r==fr)return c[R]-c[L];
    int mid=l+r>>1;
    if(fr<=mid)return query(lc[L],lc[R],l,mid,fl,fr);
    if(fl>mid)return query(rc[L],rc[R],mid+1,r,fl,fr);
    return query(lc[L],lc[R],l,mid,fl,mid)+query(rc[L],rc[R],mid+1,r,mid+1,fr);
}
LL ans=0;
void solve(int l,int r)
{
    if(l>=r)return;
    int mid=query_mn(l+1,r);
    solve(l,mid-1),solve(mid,r);
    int nl=mid-l,nr=r-l+1-nl,mn=height[mid];
    if(nl<nr)
    {
        for(int i=l;i<=mid-1;i++)
        ans+=((LL)query(root[mid-1],root[r],1,n,max(1,sa[i]-mn-L),max(1,sa[i]-L)-1)),
        ans+=((LL)query(root[mid-1],root[r],1,n,min(n,sa[i]+L)+1,min(n,sa[i]+mn+L)));
    }
    else
    {
        for(int i=mid;i<=r;i++)
        ans+=((LL)query(root[l-1],root[mid-1],1,n,max(1,sa[i]-mn-L),max(1,sa[i]-L)-1)),
        ans+=((LL)query(root[l-1],root[mid-1],1,n,min(n,sa[i]+L)+1,min(n,sa[i]+mn+L)));
    }
}
int main()
{
    n=read(),L=read();
    Log[1]=0;for(int i=2;i<=n;i++)Log[i]=Log[i>>1]+1;
    for(int i=1;i<=n;i++)a[i]=read();
    for(int i=2;i<=n;i++)A[i-1].x=a[i]-a[i-1],A[i-1].id=i-1;
    sort(A+1,A+n,cmp);A[0].x=inf;
    for(int i=1;i<n;i++)
    {
        if(A[i].x!=A[i-1].x)m++;
        s[A[i].id]=m;
    }
    n--;
    get_sa(),get_height();
    root[0]=0;
    for(int i=1;i<=n;i++)insert(root[i],1,n,sa[i]),merge(root[i],root[i-1]);
    solve(1,n);
    printf("%lld",ans);
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值