线段树区间更新查询区间历史最小值,看似很简单的题意写了两天才写出来。
题意:n个数,Q次操作,每次操作对一个区间[l,r]的数同时加上C,然后输出这段区间的历史最小值。
思路:在线段树区间更新最值查询的基础上再用一个变量表示历史最小值,每次更新只需对当前最小值更新然后用当前最小值更新历史最小值。细节问题很不好处理,可能会有重复多次更新某个区间但是其下的子节点叶节点却没有更新,而是每次重复叠加了一个add值,这样就可能将最小值覆盖。所以我们在pushdown操作的时候应该注意如果当前要加的值是正数并且儿子节点的add是负数,则应先把儿子点上的add值pushdown。仔细想想还是很好理解的。
const int N=1e5+10;
struct node
{
int l,r;
ll now,mi,add;
}a[N<<2];
void pushup(int k)//回溯向上更新
{
a[k].now=min(a[k*2].now,a[k*2+1].now);
a[k].mi=min(a[k].mi,a[k].now);
}
void pushdown(int k)//向下更新
{
if(a[k].add&&a[k].l!=a[k].r)
{
if(a[k].add>0)//关键处理这种情况
{
if(a[k*2].add<0) pushdown(2*k);
if(a[k*2+1].add<0) pushdown(2*k+1);
}
a[2*k].add+=a[k].add;
a[k*2+1].add+=a[k].add;
a[k*2].now+=a[k].add;
a[k*2+1].now+=a[k].add;
a[k*2].mi=min(a[k*2].mi,a[k*2].now);
a[k*2+1].mi=min(a[k*2+1].mi,a[k*2+1].now);
a[k].mi=min(a[k*2].mi,min(a[k].mi,a[k*2+1].mi));
a[k].add=0;
}
}
void build(int l,int r,int k)
{
a[k].l=l,a[k].r=r,a[k].add=0,a[k].now=a[k].mi=INF;
if(l==r)
{
scanf("%lld",&a[k].now);
a[k].mi=a[k].now;
return ;
}
int mid=(l+r)/2;
build(l,mid,2*k);
build(mid+1,r,2*k+1);
pushup(k);
}
void update(int l,int r,ll c,int k)
{
if(l<=a[k].l&&a[k].r<=r)
{
pushdown(k);
a[k].add+=c;
a[k].now+=c;
a[k].mi=min(a[k].mi,a[k].now);
return ;
}
pushdown(k);
int mid=(a[k].l+a[k].r)/2;
if(l<=mid) update(l,r,c,2*k);
if(r>mid) update(l,r,c,2*k+1);
pushup(k);
}
ll query(int l,int r,int k)
{
if(a[k].l==l&&a[k].r==r) return a[k].mi;
pushdown(k);
int mid=(a[k].l+a[k].r)/2;
if(r<=mid) return query(l,r,2*k);
if(l>mid) return query(l,r,2*k+1);
return min(query(l,mid,2*k),query(mid+1,r,2*k+1));
}
int main()
{
int n,q;
while(~scanf("%d",&n))
{
build(1,n,1);
scanf("%d",&q);
while(q--)
{
int l,r;
ll c;
scanf("%d%d%lld",&l,&r,&c);
update(l,r,c,1);
printf("%lld\n",query(l,r,1));
}
}
return 0;
}
提供一组数据:
5
1 2 3 4 5
5
1 3 -9
output:-8
1 4 3
output:-8
2 5 1
output:-7