[bzoj2124]等差子序列_线段树_hash

等差子序列 bzoj-2124

题目大意:给定一个1~n的排列,问是否存在3个及以上的位置上的数构成连续的等差子序列。

注释:$1\le n\le 10^4$。

想法:这题就相当于是否存在3个数i,j,k,a[i]表示i位置上的数,使得:i<j<k且a[k]-a[j]=a[j]-a[i]。

引理1:一个满足条件的序列,一定是x-a,x,x+a的形式。

证明:滚。

引理2:两个数x和y,如果y不在x之前出现,那么y一定在x之后出现。

证明:因为是1~n的排列,所以y必然出现,证毕。

引理3:如果存在2个数,x出现了,y出现了,2*y-x没出现,那么一定存在满足条件的解。

证明:由引理2,显然。

那么,我们对于当前桶维护权值线段树,此时:假设当前指针p在(n+1)>>1左侧,如果1~2*p-1在桶上构成的01字符串不是关于p回文的(此处p处桶已经存在),那么说明两个位置关于p对称且一个为1,一个为0。那么,为0的位置有引理3必定会在之后的某一个位置出现,这是一定是存在满足条件的序列的。换句话说,我们只需要判断每次枚举到的数在桶上的位置左右在长度极大的情况下是不是关于该位置回文的。这时,我们只需要对于桶内的每一个点维护向前、向后的hash前(后)缀和,O(1)判断即可。那么,我们如何更新呢?我们发现,当前位置有0变成1,只会使得小于这个数的后缀和和大于这个数的前缀和的hash值发生变化。那,变化了多少呢?假设当前位置是p,hash的增量是base,显然后面的数每个位置的hash值都会增加$base^{p-1}$,前面的数的后缀和都会增加$base^{n-p}$,而这个过程可以用线段树在log的时间内维护。每次就是区间加,和单点查询,复杂度是O(nlogn)。

最后,附上丑陋的代码... ...

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define N 100010 
#define lson pos<<1
#define rson pos<<1|1
using namespace std;
typedef unsigned long long ull;
const ull base=233;
int cases,n,a[N];
ull hash1[N<<2],hash2[N<<2],p[N];
void fix(int pos,int l,int r,int x)
{
    if(l==r)
    {
        hash1[pos]=hash2[pos]=base;
        return;
    }
    int mid=(l+r)>>1;
    if(x<=mid) fix(lson,l,mid,x);
    else fix(rson,mid+1,r,x);
    hash1[pos]=hash1[lson]*p[r-mid]+hash1[rson];
    hash2[pos]=hash2[rson]*p[mid-l+1]+hash2[lson];
}
ull gethash(int pos,int l,int r,int x,int y,int v)
{
    if(x<=l&&r<=y)
    {
        if(v==1)return hash1[pos];
        return hash2[pos];
    }
    int mid=(l+r)>>1;
    if(y<=mid) return gethash(lson,l,mid,x,y,v);
    if(x>mid) return gethash(rson,mid+1,r,x,y,v);
    ull lre=gethash(lson,l,mid,x,y,v),rre=gethash(rson,mid+1,r,x,y,v);
    if(v==1)return lre*p[min(y,r)-mid]+rre;
    return rre*p[mid-max(x,l)+1]+lre;
}
int main()
{
    cin >> cases ; p[0]=1;
    for(int i=1;i<=10000;i++) p[i]=p[i-1]*base;
    while(cases--)
    {
        memset(hash1,0,sizeof hash1);
        memset(hash2,0,sizeof hash2);
        memset(a,0,sizeof a);
        scanf("%d",&n);
        for(int i=1;i<=n;++i) scanf("%d",&a[i]);
        int flag=0;
        for(int len,i=1;i<=n;++i)
        {
            fix(1,1,n,a[i]);
            len=min(a[i],n-a[i]+1);
            if(gethash(1,1,n,a[i]-len+1,a[i],1)!=gethash(1,1,n,a[i],a[i]+len-1,2))
            {
                flag=1;
                break;
            }
        }
        if(flag)puts("Y");
        else puts("N");
    }
    return 0;
}

小结:线段树真tm牛逼,hash更牛逼... ...

posted @ 2018-07-18 23:12  JZYshuraK_彧  阅读(251)  评论(0编辑  收藏  举报