AT_abc368_g Add and Multiply Queries
这场ABC水死了
注意到一个关键条件,就是答案最大不超过 \(10^{18}\) 个。也就意味着 \(b\) 数组中每个询问最多只有 \(64\) 个大于等于 \(2\)。
我们直接维护一个平衡树。
将所有 \(b\) 大于等于 \(2\) 的下标全放到平衡树中。
修改就是看这个数的状态考虑放不放入平衡树。
查询就是看 \(l\) 的后继,一个一个往下推。
这样 \(l\) 到 \(r\) 就成了许多段。
分类讨论,\(b\) 大于等于 \(2\) 的位置看是加 \(a\) 好还是选乘 \(b\) 好。另外的区间直接用线段树加上 \(a\) 的区间值就可以。
特别注意 \(l\) 这个点只可以选加 \(a\) 操作。
这个时候就有同学问了,不会平衡树怎么办,我为大家推荐值域线段树。把没个 \(b\) 大于等于 \(2\) 的位置搞成一,然后在线段树上二分就可以得到后继。
像线段树上二分这种算法,还是太难了,那么我为大家推荐是 \(set\),求后继的方法也是直接二分。
如果 \(set\) 还是不会,我为大家推荐用链表。开两个链表,一个是 \(b\) 大于等于 \(2\) 的位置,另一个是 \(b\) 不大于等于 \(2\) 的位置。通过两个链表,修改变得简单了。但在查询的时候,我们要在第一个链表上时间才合法,如果查的点在第一个上,直接跳,反之我们考虑维护第二个链表后面有多少个连续的,就可以了。
这不就是微信小广告吗
#include<bits/stdc++.h>
#define ll long long
#define int ll
#define debug(x) cout<<#x<<":"<<x,puts("");
#define FOR(i,a,b) for(ll i=(a); i<=(b); ++i)
#define ROF(i,a,b) for(ll i=(a); i>=(b); --i)
//
//
//
using namespace std;
inline ll read() {
ll f = 0, t = 0;
char c = getchar();
while (!isdigit(c)) t |= (c == '-'), c = getchar();
while (isdigit(c)) f = (f << 3) + (f << 1) + c - 48, c = getchar();
return t ? -f : f;
}
#define lw(x) (x<<1)
#define rw(x) (x<<1|1)
#define mid (l+r>>1)
const int MX = 5e5 + 10;
int tree[MX * 4];
void Push_up(int x) {
tree[x] = tree[lw(x)] + tree[rw(x)];
}
int a[MX],b[MX];
void build(int x,int l,int r) {
if(l == r) {
tree[x] = a[l];
return;
}
build(lw(x),l,mid);
build(rw(x),mid+1,r);
Push_up(x);
}
int find1(int x,int l,int r,int ln,int rn) {
if(ln>rn) return 0;
if(ln<=l && r<=rn) {
return tree[x];
}
int A = 0;
if(ln<=mid) A += find1(lw(x),l,mid,ln,rn);
if(mid<rn) A += find1(rw(x),mid+1,r,ln,rn);
Push_up(x);
return A;
}
void change(int x,int l,int r,int id,int w) {
if(l==r) {
tree[x] = w;
return ;
}
if(id<=mid) change(lw(x),l,mid,id,w);
else change(rw(x),mid+1,r,id,w);
Push_up(x);
}
int siz[MX], w[MX], v[MX], top;
int add(int x) {
siz[++top] = 1;
w[top] = x;
v[top] = rand();
return top;
}
int tr[MX][3];
void push_up(int x) {
siz[x] = 1 + siz[tr[x][0]] + siz[tr[x][1]];
}
int merge(int x, int y) {
if (!x || !y) return x + y;
if (v[x] < v[y])
//遵循堆的原则
{
tr[x][1] = merge(tr[x][1], y);
push_up(x);
return x;
} else {
tr[y][0] = merge(x, tr[y][0]);
push_up(y);
return y;
}
}
void split(int now, int k, int &x, int &y) {
if (!now) {
x = y = 0;
return;
}
if (w[now] <= k) x = now, split(tr[now][1], k, tr[now][1], y);
else y = now, split(tr[now][0], k, x, tr[now][0]);
push_up(now);
}
int find(int x, int k) {
while (k != siz[tr[x][0]] + 1) {
if (k <= siz[tr[x][0]]) x = tr[x][0];
else k -= siz[tr[x][0]] + 1, x = tr[x][1];
}
return x;
}
int root = 0,x,y,z;
int back(int a) {
split(root, a, x, y);
int ans = w[find(y, 1)];
root = merge(x, y);
return ans;
}
signed main() {
ios::sync_with_stdio(0), cout.tie(0);
int n = read();
FOR(i,1,n) a[i] = read();
FOR(i,1,n) b[i] = read();
// root = 0, x,/ y, z;
FOR(i,1,n) {
if(b[i] != 1) {
split(root, i, x, y);
root = merge(merge(x, add(i)), y);
}
}
build(1,1,n);
int Q = read();
while(Q--) {
int op = read();
if(op == 1) {
int id = read(),w = read();
a[id] = w;
change(1,1,n,id,w);
}
if(op == 2) {
int id = read(), w = read();
if(b[id] != 1) {
split(root, id, x, z), split(x, id - 1, x, y);
y = merge(tr[y][0], tr[y][1]);
root = merge(merge(x, y), z);
}
if(w != 1) {
split(root, id, x, y);
root = merge(merge(x, add(id)), y);
}
b[id] = w;
}
if(op == 3) {
int l = read(), r = read();
int ans = a[l],p = l + 1;
while(back(l) <= r && back(l) != 0) {
l = back(l);
ans += find1(1,1,n,p,l - 1);
if(ans * b[l] > ans + a[l]) ans = ans * b[l];
else ans = ans + a[l];
p = l + 1;
}
if(l <= r) ans += find1(1,1,n,l+1,r);
cout<<ans<<'\n';
}
}
return 0;
}