练习记录(权值线段树or set)

本文解析了一道牛客网比赛题目,采用树状数组和线段树解决区间查询问题,通过预处理找到每个元素的前后两个关键位置,实现高效求解特定条件下的子数组数量。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

牛客练习赛的一道题目
在这里插入图片描述 先想到一个思路,对于一个固定的左端点,右边第一个比他大的数下标为i,第二个为j,则右端点必然在[i,j-1]之间,同时对于每一个右端点满足要求的左端点也在一个区间中,还需要检查固定的左端点是否在该区间中。直接检查的话会超时,可以检索每个点为右端点,对应的区间中有多少成立的左端点转化为区间和问题,可用树状数组解决。而某个点A作为左端点成立时仅在右端点为[i,j-1]是成立,只需要在枚举到以i为右端点时将A激活(即树状数组add(A,1)),在枚举j时再消除他的影响即可。最后还要解决对于i找前两个a[i]大的数的下标和后两个比a[i]小的下标,可以分别按照a[i]值由大到小,由小到大插入下标,利用权值线段树或set查找前驱和后继。

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e6+5;
int n,a[maxn],pre[maxn][2],suf[maxn][2],c[maxn],sa[maxn];
void add(int x,int v)
{
    while(x<=n)
    {
        c[x]+=v;
        x+=x&(-x);
    }
}

int query(int x)
{
    int ans=0;
    while(x)
    {
        ans+=c[x];
        x-=x&(-x);
    }
    return ans;
}

int tree[4*maxn];
void insert(int o,int l,int r,int x)
{
    if(l==r)
    {
        tree[o]+=1;
        return;
    }
    int lc=2*o,rc=2*o+1,mid=l+(r-l)/2;
    if(x<=mid) insert(lc,l,mid,x);
    else insert(rc,mid+1,r,x);
    tree[o]=tree[lc]+tree[rc];
}

int findpre(int o,int l,int r,int x)
{
    if(x<1) return 0;
    if(l==r) 
    {
        if(tree[o])
        return l;
        else return 0;
    }
    int lc=2*o,rc=2*o+1,mid=l+(r-l)/2;
    if(x<=mid) return findpre(lc,l,mid,x);
    else
    {
        int ans=0;
        if(tree[rc])
        ans=findpre(rc,mid+1,r,x);
        if(!ans)
        ans=findpre(lc,l,mid,x);
        return ans;
    } 
}

int findsuf(int o,int l,int r,int x)
{   
    if(x>n) return n+1;
    if(l==r)
    {
        if(tree[o]) return l;
        else return n+1;
    }
    int lc=2*o,rc=lc+1,mid=l+(r-l)/2;
    if(x>mid) return findsuf(rc,mid+1,r,x);
    else
    {
        int ans=n+1;
        if(tree[lc]) ans=findsuf(lc,l,mid,x);
        if(ans>n)
        ans=findsuf(rc,mid+1,r,x);
        return ans;
    }
}
vector<int> mp[maxn][2];
int main()
{
    //freopen("in.txt","r",stdin);
    scanf("%d",&n);
    for(int i=1;i<=n;i++) 
    {   
        scanf("%d",&a[i]);
        sa[a[i]]=i;
    }
    for(int i=1;i<=n;i++)
    { 
        suf[sa[i]][0]=findsuf(1,1,n,sa[i]+1);
        suf[sa[i]][1]=findsuf(1,1,n,suf[sa[i]][0]+1);
        insert(1,1,n,sa[i]);
    }
    memset(tree,0,sizeof(tree));
    for(int i=n;i>=1;i--)
    {
        pre[sa[i]][0]=findpre(1,1,n,sa[i]-1);
        pre[sa[i]][1]=findpre(1,1,n,pre[sa[i]][0]-1);
      //  printf("%d %d\n",pre[sa[i]][0],pre[sa[i]][1]);
        insert(1,1,n,sa[i]);
    }
    for(int i=1;i<=n;i++)
    { 
       // printf("%d %d %d\n",i,pre[i][0],pre[i][1]); 
        mp[suf[i][0]][0].push_back(i);
        mp[suf[i][1]][1].push_back(i);
    }
    long long  ans=0;
    for(int i=1;i<=n;i++)
    {
        for(int j=0;j<mp[i][0].size();j++)
        {
            add(mp[i][0][j],1);
        }
        for(int j=0;j<mp[i][1].size();j++)
        {
            add(mp[i][1][j],-1);
        }
        ans+=query(pre[i][0])-query(pre[i][1]);
    }
    printf("%lld\n",ans);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值