分析:原数组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;
}