推出公式就能做,讲过题基本上难度也瞬间降低了一个等级。不过对于我这个初学者来说写起来还是很费劲(线段树教做人)。
讲下公式吧:
我们设读入的 i 位置的数组b[ ]前缀和为B[i]
区间推平位置的判断标准:a[i]+b[i]<a[i+1]区间推平位置的判断标准 : a[i] + b[i] <a[i+1]区间推平位置的判断标准:a[i]+b[i]<a[i+1]
即:a[i]+B[i]−B[i−1]<a[i+1]即:a[i]+B[i]-B[i-1]<a[i+1]即:a[i]+B[i]−B[i−1]<a[i+1]
即:a[i]−B[i−1]<a[i+1]−B[i]即 :a[i]-B[i-1]<a[i+1]-B[i]即:a[i]−B[i−1]<a[i+1]−B[i]
只要简单处理b[ ]的下标即可统一a[i]和B[i]的下标,并且因为公式化后与b[ ]数组值完全没关系,所以在读入的时候直接处理出前缀和B[ ],但是在本次代码中我仍旧引用b[ ]的数组名。
处理操作1时需要找到第一个大于a[i]+b[i]+x的位置,然后区间推平,更新成a[i]+b[i]+x的大小,但是由于每次查询都是查询a[ ]数组的区间和,所以我们可以围绕a[ ]数组建树,然后每次因为只用判断位置和区间推平,这样建树其实不会带来太多麻烦。由于区间内都是小于该值的,所以等于是在维护t[i]的区间最大值。
值得一提的是,我们更新后t[ ]会一直处于有序递增的状态。所以也就满足了二分查找的条件。找到给定值的位置的复杂度也降低了。O(logn)
操作2就是求个区间和,线段树基本操作。
几个细节注意一下
pushdown的时候如果遇到懒标记为-inf,则说明懒标记不能动,这和以往懒标记初始化为0不同,如果不跳过可能会影响到儿子的更新
我发现在推平的时候,发现如果是循环相加每个b[i]的值就会很慢,不如先求b[i]的前缀和sum[i],需要的时候差分相减就有区间和了。
二分查找后要更新时要减去当前b[x]值,因为之前二分查找的过程中是与tr[i]+b[x]比较,得到的key值相当于加过一遍b[x],所以到时候要减去一次。(我在这里对拍了半天本地都过不去)
#include<bits/stdc++.h>
#define maxn 100005
#define maxm 200005
#define FOR(a, b, c) for(int a=b; a<=c; a++)
#define hrdg 1000000007
#define inf 2147483647
#define llinf 9223372036854775807
#define ll long long
#define pi acos(-1.0)
#define ls p<<1
#define rs p<<1|1
using namespace std;
inline int read()
{
char c=getchar();long long x=0,f=1;
while(c<'0'||c>'9'){if(c=='-') f=-1; c=getchar();}
while(c>='0'&&c<='9'){x=x*10+c-'0'; c=getchar();}
return x*f;
}
int n, m, type, x, y;
ll key;
ll a[maxn], b[maxn], sum[maxn];
ll tr[maxn<<2], tag[maxn<<2];
void pushup(int p) {tr[p] = tr[ls] + tr[rs];} //都是一般的线段树操作
void build(ll p, ll l, ll r){
tag[p] = -inf;
if(l == r)
{
tr[p] = a[l];
return;
}
ll mid = (l+r) >> 1;
build(ls, l, mid);
build(rs, mid+1, r);
pushup(p);
}
void load(ll p, ll l, ll r, ll k){
tr[p] = sum[r] - sum[l-1] + (r-l+1) * k; //差分推平
tag[p] = k;
}
void pushdown(ll p, ll l, ll r){
if(tag[p] != -inf) //如果tag[p]仍为-inf证明没有更新这个点,不管它
{
ll mid = (l+r) >> 1;
load(ls, l, mid, tag[p]);
load(rs, mid+1, r, tag[p]);
tag[p] = -inf;
}
}
void update(ll nl, ll nr, ll l, ll r, ll p, ll k){
if(nl > nr) return;
if(nl <= l && nr >= r)
{
load(p, l, r, k);
return;
}
pushdown(p, l, r);
ll mid = (l + r) >> 1;
if(nl <= mid)
update(nl, nr, l, mid, ls, k);
if(nr >= mid+1)
update(nl, nr, mid+1, r, rs, k);
pushup(p);
}
ll query(ll nl, ll nr, ll l, ll r, ll p){
ll ret = 0;
if(nl <= l && nr >= r)
return tr[p];
pushdown(p, l, r);
ll mid = (l + r) >> 1;
if(nl <= mid)
ret += query(nl, nr, l, mid, ls);
if(nr >= mid+1)
ret += query(nl, nr, mid+1, r, rs);
return ret;
}
ll bin_search(ll p, ll x){
key = query(p, p, 1, n, 1); //先找到在tr[]中的位置
ll l = p, r = n;
while(l < r) //二分查找大于等于的点
{
ll mid = (l + r + 1) >> 1;
if(b[mid] - b[p] + key + x > query(mid, mid, 1, n, 1))
l = mid;
else
r = mid - 1;
}
return l; //返回l==r时就是所求位置(一般操作)
}
int main()
{
n = read();
FOR(i, 1, n)
a[i] = read();
build(1, 1, n);
FOR(i, 2, n)
{
b[i] = read();
b[i] += b[i-1]; //直接处理成和的形式
}
FOR(i, 2, n)
sum[i] = sum[i-1] + b[i]; //提前处理前缀和,后面方便推平
m = read();
while(m--)
{
type=read(); x=read(); y=read();
if(type == 1)
{
ll pos = bin_search(x, y);
update(x, pos, 1, n, 1, key-b[x]+y); //要减去b[x]才是待加的k
}
else
printf("%lld\n", query(x, y, 1, n, 1));
}
return 0;
}
/*
3
1 2 3
1 -1
5
2 2 3
1 1 2
2 1 2
1 3 1
2 2 3
*/