CF992E Nastya and King-Shamans (线段树+暴力查询+剪枝)

本文介绍了如何通过构建线段树并利用d[i]数组的规律,解决原数组a[i]的前缀和sum[i]中,查询满足d[i]==0的下标问题。通过剪枝优化,将查询时间复杂度降低到O(n*logn*logsum)。核心在于理解d[i]的增长特性并运用数据结构进行高效处理。
摘要由CSDN通过智能技术生成

题目链接

在这里插入图片描述

分析:原数组a[i] 前缀和数组sum[i] ,
设数组d[i]=a[i]-sum[i-1],由于a[i]是属于[0,1e9]的,所以d[i]数组的值其实分布的特别有规律,满足d[i]==0的下标不会超过logn级别,为什么呢? 很简单,d[i]==0意味着a[i]是sum[i-1]的两倍,每次等于0都是两倍增长,而最大值是1e15,所以是趋于log级别。

所以问题就等价于每次修改数组d[i] 、 d [i+1,n] 也就是单点修改+区间修改。 查询的时候需要查的就是 是否存在d[i]==0,既然知道了这个性质,我们就可以暴力单独查询了,并且区间最大值小于0的区间直接剪枝剪掉。

我们用线段树维护数组d[i]的区间最大值,
每次查询就单点查询+剪枝(区间最大值小于0就直接return)

由于剪枝时间复杂度会降到O(n * logn * logsum) (sum为常数)

原因:d[i]==0 <=> sum[i]-sum[i-1]-sum[i-1]==0 <=> sum[i]==2*sum[i-1] 
如果存在一个i满足条件 那么对于每个满足条件的i,sum[i]每次翻倍 
所以只能有logsum数量的位置满足答案。 
所以加上剪枝操作后查询的叶子节点数目是logsum级别的

所以总结一下,思路就是先对d[i] (定义成a[i]-sum[i-1])建线段树维护区间最大值。

然后把区间查询暴力改成单点查询,但是通过剪枝操作把每次最多查询的点将成了常量级别, 时间复杂度就是O(n * logn * logsum)

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn = 2e5+7;
const ll inf = 34359738370;
int n,m;
ll a[maxn];
ll sum[maxn];
//对d[i](=a[i]-sum[i-1]) 建线段树维护区间最大值  单点修改 区间修改 单点查询
struct 
{
    ll mmax;
}tree[maxn<<2];
ll tag[maxn<<2];
inline int lc(int rt)
{
    return rt<<1;
}
inline int rc(int rt)
{
    return rt<<1 | 1;
}
inline void pushup(int rt)
{
    tree[rt].mmax=max(tree[lc(rt)].mmax,tree[rc(rt)].mmax);
    return ;
}
inline void build(int rt,int l,int r)
{
    if(l == r)
    {
        tree[rt].mmax=a[l]-sum[l-1];
        return ;
    }
    int mid=(l+r)>>1;
    build(lc(rt),l,mid);
    build(rc(rt),mid+1,r);
    pushup(rt);
    return ;
}
inline void change(int rt,ll v)
{
    tree[rt].mmax+=v;
    tag[rt]+=v;
    return ;
}
inline void pushdown(int rt,int l,int r)
{
    if(tag[rt])
    {
        int mid=(l+r)>>1;
        change(lc(rt),tag[rt]);
        change(rc(rt),tag[rt]);
        tag[rt]=0;
    }
    return ;
}
inline void updata(int rt,int l,int r,int vl,int vr,int v)//[vl,vr]每个数+v
{
    if(l>vr || r<vl) return ;
    if(vl<=l && r<=vr) 
    {
        change(rt,v);
        return ;
    }
    int mid=(l+r)>>1;
    pushdown(rt,l,r);
    updata(lc(rt),l,mid,vl,vr,v);
    updata(rc(rt),mid+1,r,vl,vr,v);
    pushup(rt);
    return ;
}
inline int query(int rt,int l,int r)
{
    if(tree[rt].mmax<0) return -1;//通过区间最值剪枝
    if(l == r)
    {
        if(tree[rt].mmax==0) return l;
        else return -1;
    }
    int mid=(l+r)>>1;
    pushdown(rt,l,r);
    int t=query(lc(rt),l,mid);
    if(t != -1) return t;
    return query(rc(rt),mid+1,r);
}
int main()
{
    scanf("%d %d",&n,&m);
    for(int i=1;i<=n;i++)
    {
        scanf("%lld",a+i);
        sum[i]=sum[i-1]+a[i];
    }
    build(1,1,n);
    while(m--)
    {
        int x,v;
        scanf("%d %d",&x,&v);
        v-=a[x];
        a[x]+=v;//记得要把a[x]变成v 因为a[i]是时时要用的
        updata(1,1,n,x,x,v);//对于d[x]而言 增加v-a[x] 
        if(x+1<=n)
            updata(1,1,n,x+1,n,-v);//对于i∈[x+1,n]而言 每个d[i]都减少了v-a[x]
        printf("%d\n",query(1,1,n));
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值