JZOJ 5557. 写诗

题目大意

给一个排列 h h ,求是否存在三元组(i,j,k),其中 i<j<k i < j < k ,使得 hihj=hjhk h i − h j = h j − h k

解题思路

有这么几种想法。
第一种,将排列中奇偶性相同的放在一块,然后寻找中点出现了没。但这种方法显然有问题。因为新加入一个数后,有 O(n2) O ( n 2 ) 种可能能够判断出答案,时间超限。
第二种,采用比较的方法,那么比较什么呢?肯定要比较序列的两边的东西。那么到底是用原序列,还是用反序列?反序列: h[h[i]]=i h ′ [ h [ i ] ] = i
如果用原序列,那么用每个数出现的位置来判定,每增加一个数,又会出现 O(n2) O ( n 2 ) 种可能的三元组。所以方案不行。
如果考虑反序列,设当前位置上的数为 x x ,则比较一下出现过的和没有出现过的数在线段树中的值。(0表示出现过,1表示没出现过)。对称地,以x为中心,左边某段的哈希值和右边某段的哈希值相等,则说明存在了三元组。
特别地,要维护两种哈希值。包括从左到右和从右到左的。

心得


这种问题模型出现过很多,大概是给n个点,然后问中点的某些信息。
如果给的是排列,那么抓住“每个数只会出现一次”来作文章。
如果知道了每个数出现了一次,那么在扫描的时候是可以清楚地知道哪些数出现了,哪些数没出现。
设目前的01状态(该数字是否出现),如果改变了其中的一位,维护正确的答案的复杂度过高,那么很多时候是行不通的。
如果扫到一个数 x x <script type="math/tex" id="MathJax-Element-111">x</script>,则它前面的全是出现的,它后面是没有出现的。根据此性质可以运用到反数组中。
中点,也是一个很重要的研究点
到底是以现在扫到的点为中点,还是通过现在扫到的点去寻找中点?研究方向的选择是很重要的。
像这道题目,如果以现在扫到的点为中点,那么在反数组中很容易根据对称性来判断出答案。


而比较两个序列的不同,可以用哈希判断。

代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define N 300010
#define mo 998244353
#define fo(i,a,b) for(i=a;i<=b;i++)
#define fd(i,a,b) for(i=a;i>=b;i--)
using namespace std;
int tr[N*4],tr1[N*4];
int i,j,k,n,m,gx,gy;
int a[N],b[N];
int _2[N];
bool bz[N];
int read(){
    int fh=1,res=0;char ch;
    while((ch<'0'||ch>'9')&&(ch^'-'))ch=getchar();
    if(ch=='-')fh=-1,ch=getchar();
    while(ch>='0'&&ch<='9')res=(res<<3)+(res<<1)+(ch^'0'),ch=getchar();
    return fh*res;
}
void upl(int ps,int l,int r){
    int wz=(l+r)>>1;
    tr[ps]=((1ll*tr[ps<<1]*_2[r-wz])%mo+tr[ps<<1|1])%mo;
}
void upr(int ps,int l,int r){
    int wz=(l+r)>>1;
    tr1[ps]=((1ll*tr1[ps<<1|1]*_2[wz-l+1])%mo+tr1[ps<<1])%mo;
}
void bul(int ps,int l,int r,int x){
    if(l==r){
        tr[ps]=x;
        return;
    }
    int wz=(l+r)>>1;
    bul(ps<<1,l,wz,x);
    bul(ps<<1|1,wz+1,r,x);
    upl(ps,l,r);
}
void bur(int ps,int l,int r,int x){
    if(l==r){
        tr1[ps]=x;
        return;
    }
    int wz=(l+r)>>1;
    bur(ps<<1,l,wz,x);
    bur(ps<<1|1,wz+1,r,x);
    upr(ps,l,r);
}
void chl(int ps,int l,int r,int x,int z){
    if(l==r){
        tr[ps]=z;
        return;
    }
    int wz=(l+r)>>1;
    if(x<=wz)chl(ps<<1,l,wz,x,z);
        else chl(ps<<1|1,wz+1,r,x,z);
    upl(ps,l,r);
}
void chr(int ps,int l,int r,int x,int z){
    if(l==r){
        tr1[ps]=z;
        return;
    }
    int wz=(l+r)>>1;
    if(x<=wz)chr(ps<<1,l,wz,x,z);
        else chr(ps<<1|1,wz+1,r,x,z);
    upr(ps,l,r);
}
int gtl(int ps,int l,int r,int x,int y){
    if(x>y)return 0;
    if(l==x&&r==y)return tr[ps];
    int wz=(l+r)>>1;
    if(y<=wz)return gtl(ps<<1,l,wz,x,y);else
    if(x>wz)return gtl(ps<<1|1,wz+1,r,x,y);else{
        long long z1=gtl(ps<<1,l,wz,x,wz),z2=gtl(ps<<1|1,wz+1,r,wz+1,y);
        return ((z1*_2[y-wz])%mo+z2)%mo;
    }
}
int gtr(int ps,int l,int r,int x,int y){
    if(x>y)return 0;
    if(l==x&&r==y)return tr1[ps];
    int wz=(l+r)>>1;
    if(y<=wz)return gtr(ps<<1,l,wz,x,y);else
    if(x>wz)return gtr(ps<<1|1,wz+1,r,x,y);else{
        long long z1=gtr(ps<<1,l,wz,x,wz),z2=gtr(ps<<1|1,wz+1,r,wz+1,y);
        return ((z2*_2[wz-x+1])%mo+z1)%mo;
    }
}
int main(){
    freopen("poem.in","r",stdin);
    freopen("poem.out","w",stdout);
    _2[0]=1;fo(i,1,300000)_2[i]=(_2[i-1]*2)%mo;
    n=read();
    fo(i,1,n)a[i]=read();
    bul(1,1,n,1);
    bur(1,1,n,1);
    chl(1,1,n,a[1],0);
    chr(1,1,n,a[1],0);
    fo(i,2,n-1){
        k=a[i]-1<n-a[i]?a[i]-1:n-a[i];
        gx=gtl(1,1,n,a[i]-k,a[i]-1);
        gy=gtr(1,1,n,a[i]+1,a[i]+k);
        if(gx!=gy){
            printf("YES");
            return 0;
        }
        chl(1,1,n,a[i],0);
        chr(1,1,n,a[i],0);
    }
    printf("NO");
    return 0;
}
  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值