题意:n个数,有q次询问,每次询问有两个操作 1.在区间【l, r】找到比v大的第一个数的下标 2.把【l,r】的数全部加上v。
思路: 裸线段树的题
写法1:线段树
#include <cstdio>
#include <cstring>
#include <iostream>
#define mem(a) memset(a, 0, sizeof(a))
using namespace std;
const int inf = 0x3f3f3f3f;
const int MAXN =100100;
typedef long long LL;
LL a[MAXN];
LL lazy[MAXN<<2];
struct T
{
int l, r;
LL m;
} tree[MAXN<<2];
void pushdown(int k)
{
if(lazy[k])
{
tree[k<<1].m+=lazy[k];
tree[k<<1|1].m+=lazy[k];
lazy[k<<1]+=lazy[k];
lazy[k<<1|1]+=lazy[k];
lazy[k]=0;
}
return ;
}
void pushup(int k)
{
tree[k].m=max(tree[k<<1].m, tree[k<<1|1].m);
}
void build(int l, int r, int k)
{
tree[k].l=l;
tree[k].r=r;
tree[k].m=0;
if(l==r)
{
tree[k].m=a[l];
return;
}
int mid = (l+r)>>1;
build(l, mid, k<<1);
build(mid+1, r, k<<1|1);
pushup(k);
}
void update(int l, int r, int k, LL w)
{
if(tree[k].l>=l&&tree[k].r<=r)
{
tree[k].m+=w;
lazy[k]+=w;
return;
}
pushdown(k);
int mid = (tree[k].l+tree[k].r)>>1;
// if(l<=mid)
// update(l, r, k<<1, w);
// if(r>mid)
// update(l, r, k<<1|1, w);
//两种写法都可以。
if(l>mid)
update(l, r, k<<1|1, w);
else if(r<=mid)
update(l,r,k<<1,w);
else
{
update(l, mid, k<<1, w);
update(mid+1, r, k<<1|1, w);
}
pushup(k);
return ;
}
int query(int l, int r, int k, LL c)
{
if(tree[k].m<c)
return -1;
if(tree[k].l==tree[k].r)
return tree[k].l;
pushdown(k);
pushup(k);
int mid = (tree[k].l+tree[k].r)>>1;
int x=-1, y=-1;
if(l<=mid)
x=query(l, r, k<<1, c);
if(x!=-1)return x;
if(r>mid)
y=query(l, r, k<<1|1, c);
return y;
}
int main()
{
int t, q;
scanf("%d",&t);
while(t--)
{
int n;
memset(lazy, 0, sizeof(lazy));
scanf("%d %d",&n, &q);
for(int i=1; i<=n; ++i)
scanf("%lld", &a[i]);
build(1, n, 1);
int op, l, r;
LL v;
while(q--)
{
scanf("%d %d %d %lld", &op, &l,&r, &v);
if(op==1)
printf("%d\n", query(l, r, 1, v));
else
update(l, r, 1, v);
}
}
return 0;
}
写法二:分块
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 100005;
typedef long long LL;
LL a[MAXN], b[MAXN];
LL add[MAXN], mx[MAXN];
int n, block;
void init()
{
block = sqrt(n);
int num = n/block;
for(int i=1; i<=num; ++i)
for(int j=(i-1)*block+1; j<=i*block; ++j)
mx[i]=max(a[j], mx[i]);//维护每一块的最大值
if(n%num) num++;//如果不湿刚好分完块
for(int i=num; i<=num; ++i)
for(int j=(num-1)*block+1; j<=n; ++j)
mx[i]=max(a[j],mx[i]);//对最后一块找最大
// for(int i=1;i<=num;++i)
// printf("%lld\n", mx[i]);
for(int i=1; i<=n; ++i)
b[i]=(i-1)/block+1;//标记下标i在第几块
}
void update(int l,int r,LL k)
{
int lq=b[l];
int rq=b[r];
//printf("%d %d\n", b[l], b[r]);
if(lq==rq)//如果在同一块, 暴力sqrt(n)更新
{
for(int i=l; i<=r; i++)
{
a[i]+=k;
if(a[i]+add[lq]>mx[lq])
mx[lq]=a[i]+add[lq];
}
return ;
}
for(int i=l; i<=lq*block; i++)
{
a[i]+=k;
if(a[i]+add[lq]>mx[lq])
mx[lq]=a[i]+add[lq];
}
for(int i=(rq-1)*block+1; i<=r; i++)
{
a[i]+=k;
if(a[i]+add[rq]>mx[rq])
mx[rq]=a[i]+add[rq];
}
//如果区间跨度大于两块,也就是r-l>1,直接更新块(分块的核心)
if(rq-lq>1)
{
for(int i=lq+1; i<rq; i++)
{
add[i]+=k;
mx[i]+=k;
}
}
}
LL query(int l,int r,LL k)
{
int lq=b[l];
int rq=b[r];
if(lq==rq)//只有一块
{
for(int i=l; i<=r; i++)
{
if(a[i]+add[lq]>=k) return i;
}
return -1;
}
//从左向右找,找到直接返回下标
for(int i=l; i<=lq*block; i++)
{
if(a[i]+add[lq]>=k) return i;
}
if(rq-lq>1)//有多块
{
for(int i=lq+1; i<rq; i++)
{
if(mx[i]>=k)
{
for(int j=(i-1)*block+1; j<=i*block; j++)
{
if(a[j]+add[i]>=k) return j;
}
}
}
}
for(int i=(rq-1)*block+1; i<=r; i++)
{
if(a[i]+add[rq]>=k) return i;
}
return -1;
}
int main()
{
int t;
scanf("%d", &t);
while(t--)
{
int q;
memset(add, 0, sizeof(add));
memset(mx, 0, sizeof(mx));
scanf("%d %d", &n, &q);
for(int i=1; i<=n; ++i)
scanf("%lld", &a[i]);
init();
while(q--)
{
int op, l,r;
LL k;
scanf("%d%d%d%lld",&op,&l,&r,&k);
if(op==1)
printf("%lld\n",query(l,r,k));
else
{
update(l,r,k);
// for(int i=1;i<=n;++i)
// printf("%lld%c", a[i]+add[b[i]], " \n"[i==n]);
}
}
}
return 0;
}