牛客周赛 Round 38 F.小苯的回文询问【挖掘性质等价转换+线段树】

文章讲述了如何通过挖掘数组的性质并使用线段树数据结构来解决一个关于数组中是否存在回文子序列的问题,尤其是在给定区间[l,r]内判断是否存在长度大于2的回文子序列。关键思路是通过哈希表记录每个数字的出现位置,以及正确处理连续相同数字的情况以避免无效区间。
摘要由CSDN通过智能技术生成

原题链接:https://ac.nowcoder.com/acm/contest/78292/F

时间限制:C/C++ 2秒,其他语言4秒
空间限制:C/C++ 262144K,其他语言524288K
64bit IO Format: %lld

题目描述

小苯有一个长度为 n 的数组 a,他定义一个数组是好数组,当且仅当该数组是一个回文数组,且长度严格大于 2。

他现在进行了 q 次询问,每次询问都给出一段区间 [l,r],他想知道 a 在这一段区间中是否存在一个子序列是一个好数组,请你帮帮他吧。

输入描述:

输入包含 q+2 行。
第一行两个正整数 n,q (1≤n≤10^5), (1≤q≤2×10^5),以空格分隔,分别表示小苯拥有的数组的长度,以及他的询问次数。
第二行n 个正整数 ai (1≤ai≤10^9),表示数组 a 的元素。
接下来 q 行,每行两个正整数 [l,r] (1≤l≤r≤n),以空格分隔,表示小苯每次询问的区间。

输出描述:

输出包含 q 行,如果对于当前询问的区间,存在一个好子序列是一个好数组,则输出 "YES",否则输出 "NO"。(不含双引号)

解题思路:

首先这个题目需要挖掘一些性质然后进行等价转换才好处理,对于某个子序列要存在严格大于2的回文串,也就是存在某个数出现了俩次及以上并且这个数出现的位置存在俩个位置的距离大于1,例如[1,2,1]中1出现了俩次,并且pos1=1,pos2=3俩个位置距离大于1,所以满足要求,对于[1,1,2]虽然1出现了俩次,但是俩个1的距离为1,所以不满足要求,对于[1,1,1]中1出现了三次,其中pos1=1,pos2=2,pos3=3,虽然dist(pos1,pos2)=dist(pos2,pos3)=1,但是要注意到dist(pos1,pos3)=2>1所以这个也是符合要求的,所以我们可以对相邻的相同数之间进行建立区间,注意如果同种元素相邻位置差如果为1,那么就不能建区间,但是此时需要注意一个点,我就是没有注意到这里点导致wa了,就是对于[1,1,1]这种情况,虽然第一个1和第二个1之间还有第二个1和第三个1之间距离都为1,我们是不能建区间的,但是此时可以发现我们可以在第一个1和第三个1之间是可以建立一个区间的,我就是没有考虑到这一点导致wa了,这里还有一点点需要考虑,就是如果对于所有距离大于1的同种元素都建立区间,那区间就太多了,实际上有一些区间是没有意义的,例如对于[1,1,2,1]这种情况,最后一个1和第二个1建立一个区间,此时对于最后一个1和第一个1建立区间就是没有意义的,因为最后一个1和第二个1建立的区间显然是这个区间的子区间,所以再建立这个大区间就没有意义了,所以我们需要知道每一种数的上俩个出现位置,由于值域非常大,我们可以使用哈希表记录,然后对于每一个查询[l,r],我们就只需要判断[l,r]是否包含上面建立的某个子区间即可,我们可以找到所有区间左端点大于等于l中的区间中是否存在右端点小于等于r的区间,也就是找区间大于等于l的所有区间的右端点的最小值是否小于等于r,也就是要找某个区间的最小值,可以使用线段树进行维护。

时间复杂度:O(nlogn+qlog(n))。

空间复杂度:O(n)。

cpp代码如下:

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <unordered_map>

using namespace std;
const int N=2e5+10;
typedef long long LL;

int n,q;
int a[N];
struct Node{
    int l,r;
    int v;
}tr[N*4];
unordered_map<int,int>last1,last2;

void pushup(int u)
{
    tr[u].v=min(tr[u<<1].v,tr[u<<1|1].v);
}
void build(int u,int l,int r)
{
    tr[u]={l,r,int(1e9)};
    if(l==r)return ;
    int mid=l+r>>1;
    build(u<<1,l,mid),build(u<<1|1,mid+1,r);
    pushup(u);
}

/*
建立的区间左端点这里的值设为右端点的值
(如果有多个区间左端点为同一个点,那么值设为右端点最小值)
*/
void modify(int u,int x,int v)
{
    if(tr[u].l==x && tr[u].r==x)tr[u].v=min(tr[u].v,v);
    else {
        int mid=tr[u].l+tr[u].r>>1;
        if(x<=mid)modify(u<<1,x,v);
        else modify(u<<1|1,x,v);
        pushup(u);
    }
}

int query(int u,int l,int r)
{
    if(tr[u].l>=l && tr[u].r<=r)return tr[u].v;
    else {
        int v=1e9;
        int mid=tr[u].l+tr[u].r>>1;
        if(l<=mid)v=min(v,query(u<<1,l,r));
        if(r>mid)v=min(v,query(u<<1|1,l,r));
        return v;
    }
}
int main()
{
    cin>>n>>q;
    for(int i=1;i<=n;i++)scanf("%d",&a[i]);
    build(1,1,n);
    
    for(int i=1;i<=n;i++)
    {
        int x=a[i];
        if(!last1.count(x)){
            last1[x]=i;
        }else {
            if(!last2.count(x)){
                if(last1[x]!=i-1){
                    modify(1,last1[x],i);  //建立的区间左端点这里的值设为右端点的值(如果有多个区间左端点为同一个点,那么值设为右端点最小值)
                }
            }else {
                if(last1[x]!=i-1){
                    modify(1,last1[x],i);
                }else {
                    modify(1,last2[x],i);
                }
            }
            last2[x]=last1[x];
            last1[x]=i;
        }
    }
    
    while(q--)
    {
        int l,r;
        scanf("%d%d",&l,&r);
        int q=query(1,l,r);
        if(q<=r)puts("YES");
        else puts("NO");
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值