学习笔记 珂朵莉树/颜色段均摊
新坑。属实罕见。
这次从基础理论讲起。
简介
因为某人不是珂学家所以这里不介绍珂朵莉是什么。
珂朵莉树又名老司机树(ODT),本质上是一种使用平衡树等数据结构维护颜色段均摊的技巧。其核心思想是将值相同的一段区间合并成一个节点处理,能够方便地维护区间推平操作。
实现
定义
我们考虑最常用的 set 实现,首先来看节点定义。
struct node
{
int l, r;
mutable int v;
node(int L, int R = -1, int v = 0) : l(L), r(R), v(v) {}
bool operator<(const node &o) const { return l < o.l; }
};
set<node> s;
如上所述,每个节点是一个三元组 \((l,r,v)\),表示区间 \([l,r]\) 中的值全都是 \(v\),其中 mutable 意为“可变的”,允许我们在 set 中直接修改已有元素,而不需要重新插入。
显然区间按左端点排序,并且区间不重不漏,那么按 set 的顺序遍历就是原序列。
举个例子:
1 1 1 1 4 4 5 5 1 1 4 4
可以表示成:\((1,4,1)\) \((5,6,4)\) \((7,8,5)\) \((9,10,1)\) \((11,12,4)\)
分割
分割是珂朵莉树的核心操作。
在上面的例子中,若要将区间 \([3,7]\) 全部修改为 9,没有对应的节点,因而无法直接操作。但如果我们将 \((1,4,1)\) 分割成 \((1,2,1)\) 和 \((3,4,1)\),然后再将 \((7,8,5)\) 分割成 \((7,7,5)\) 和 \((8,8,5)\),最终得到 \((1,2,1)\) \((3,4,1)\) \((5,6,4)\) \((7,7,5)\) \((8,8,5)\) \((9,10,1)\) 和 \((11,12,4)\),这样就可以通过修改 \((3,4,1)\) \((5,6,4)\) 和 \((7,7,5)\) 完成操作。
于是总结一下,我们需要实现的操作就是对于一个位置 \(pos\),找到包含它节点 \((l,r,v)\),并分割成 \((l,pos-1,v)\) 和 \((pos,r,v)\)。
实现上很简单,找节点的过程可以二分,然后只需要删掉原来的节点,重新插入两个新节点,并返回指向右节点的迭代器备用。
代码如下:
auto split(int pos)
{
auto it = s.lower_bound(node(pos, -1, 0));
if (it != s.end() && it->l == pos)
return it;
it--;
int L = it->l, R = it->r, V = it->v;
s.erase(it);
s.insert(node(L, pos - 1, V));
return s.insert(node(pos, R, V)).first;
}
现在的 NOI 系列赛事已经支持了 auto,所以不需要手写返回类型了。
推平
推平即区间赋值,可以迅速减少节点数量,是保证复杂度的重要一环。
同样是看上面的例子,我们把 \(l\),\(r\) 所在的区间分割开来,得到两个迭代器分别指向 \((3,4,1)\) 和 \((8,8,5)\),不妨记作 \(itl\) 和 \(itr\)。
我们首先要将 \(itl\) 到 \(itr\) 之间的区间全部删掉,使用 set 自带的 erase 传入两个迭代器作参数,删除掉的区间是左闭右开的,这正好符合我们 split 函数的返回值特征,于是直接调用 s.erase(itl,itr) 就可实现删除操作。
最后插入新节点,直接 s.insert(node(l, r, v));
代码如下:
void assign(int l, int r, int v)
{
auto itr = split(r + 1), itl = split(l);
s.erase(itl, itr);
s.insert(node(l, r, v));
}
值得注意的是两个 split 操作进行的顺序错误可能会导致 RE,这里贴出 OI-Wiki 的解释。
std::set::erase方法将使指向被擦除元素的引用和迭代器失效。而其他引用和迭代器不受影响。
std::set::insert方法不会使任何迭代器或引用失效。
split操作会将区间拆开。调用split(r + 1)之后 \(r + 1\) 会成为两个新区间中右边区间的左端点,此时split左区间,必然不会访问到 \(r + 1\) 为左端点的那个区间,也就不会将其拆开,删去 \(r + 1\) 为左端点的区间,使迭代器失效。反之,先split(l),再split(r + 1),可能会把 \(l\) 为左端点的区间删去,使迭代器失效。
操作
设要操作的区间是 \([l,r]\),我们将 \(l\),\(r\) 所在的节点分割后依次遍历每一个节点即可。
举个例子,区间加的代码如下:
void add(int l, int r, int v)
{
auto itr = split(r + 1), itl = split(l);
for (auto it = itl; it != itr; it++)
it->v += v;
}
习题
来看几个习题:
数轴上有 \(n\) 个点,一开始都是黑色。每次给定区间 \([l,r]\),将这个区间的点染成白色,并求还剩多少个黑点。
怎么做都能过。
注意到有区间推平操作,可以直接当板子题。
这里给出完整代码,以后只给出关键部分的实现。
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define int long long
struct node
{
int l, r;
mutable int v;
node(int L, int R = -1, int v = 0) : l(L), r(R), v(v) {}
bool operator<(const node &o) const { return l < o.l; }
};
set<node> s;
auto split(int pos)
{
auto it = s.lower_bound(node(pos, -1, 0));
if (it != s.end() && it->l == pos)
return it;
it--;
int L = it->l, R = it->r, V = it->v;
s.erase(it);
s.insert(node(L, pos - 1, V));
return s.insert(node(pos, R, V)).first;
}
void assign(int l, int r, int v)
{
auto itr = split(r + 1), itl = split(l);
s.erase(itl, itr);
s.insert(node(l, r, v));
}
int sum(int l, int r)
{
auto itr = split(r + 1), itl = split(l);
int res = 0;
for (auto it = itl; it != itr; it++)
res = (res + (it->r - it->l + 1) * it->v);
return res;
}
const int maxn = 200'000 + 10;
int n, m;
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
cin >> n >> m;
s.insert(node(1, n, 1));
while (m--)
{
int l, r;
cin >> l >> r;
assign(l, r, 0);
cout << sum(1, n) << endl;
}
return 0;
}
珂朵莉树的起源题。
写一个数据结构支持如下操作:
- 区间加
- 区间赋值
- 区间第 \(k\) 小(数字大小相同算多次)
- 区间 \(x\) 次方和(即 \(\sum_{i=l}^{r} a_i^x \pmod y\))
前两个操作以上都讲到了,来看区间第 \(k\) 小。
我们发现每一个数字都可以用一个二元组 \((val,cnt)\) 表示,其中 \(val\) 是数值,\(cnt\) 是出现的次数。这样,我们可以通过维护一个有序的二元组集合来实现区间第 \(k\) 小的查询,实现上用 vector 存起来排下序就行了。
区间 \(x\) 次方和暴力查就行了。
实现如下。
int kth(int l, int r, int k)
{
vector<pair<int, int>> v;
auto itr = split(r + 1), itl = split(l);
for (auto it = itl; it != itr; it++)
v.push_back(make_pair(it->v, it->r - it->l + 1));
sort(v.begin(), v.end());
for (auto it = v.begin(); it != v.end(); it++)
{
k -= it->second;
if (k <= 0)
return it->first;
}
return -1;
}
int sum(int l, int r, int ex, int mod)
{
auto itr = split(r + 1), itl = split(l);
int res = 0;
for (auto it = itl; it != itr; it++)
res = (res + (it->r - it->l + 1) * qpow(it->v, ex, mod)) % mod;
return res;
}
给定一个序列,\(m\) 次操作,每次选择一个区间 \([l,r]\) 进行升序/降序排序,求最后在 \(q\) 位置上的数。
考虑二分一个答案 \(mid\),将小于 \(mid\) 的值赋成 \(0\),大于等于 \(mid\) 的值赋成 \(1\),最后查询 \(q\) 位置上的值,若这个数为 \(1\),则说明 \(mid\) 可能是答案,继续向右查找;否则向左查找。
接下来看区间排序,由于只有 \(0\) 和 \(1\),可以直接用区间求和以及区间推平组合起来代替排序节省复杂度。
然后这题就做完了。
这里给出主函数和 check 函数的实现。
const int maxn = 100'000 + 10;
int n, m;
int a[maxn];
int op[maxn], l[maxn], r[maxn];
int q;
bool check(int x)
{
s.clear();
int val = (a[1] >= x), len = 1;
for (int i = 2; i <= n;i++)
{
if((a[i]>=x)!=val)
{
s.insert(node(i - len, i - 1, val));
val = (a[i] >= x), len = 1;
}
else
len++;
}
s.insert(node(n - len + 1, n, val));
s.insert(node(n + 1, n + 1, 1));
for (int i = 1; i <= m;i++)
{
if(op[i]==0)
{
int cnt = r[i] - l[i] + 1 - sum(l[i], r[i]);
assign(l[i], l[i] + cnt - 1, 0);
assign(l[i] + cnt, r[i], 1);
}
else{
int cnt = sum(l[i], r[i]);
assign(l[i], l[i] + cnt - 1, 1);
assign(l[i] + cnt, r[i], 0);
}
}
return sum(q, q);
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
cin >> n >> m;
for (int i = 1; i <= n; i++)
cin >> a[i];
for (int i = 1; i <= m; i++)
cin >> op[i] >> l[i] >> r[i];
cin >> q;
int l = 1, r = n, ans = 0;
while (l <= r)
{
int mid = (l + r) / 2;
if (check(mid))
{
l = mid + 1;
ans = mid;
}
else
r = mid - 1;
}
cout << ans << endl;
return 0;
}
建议去看原题面。
-
对于操作 1,直接区间推平。
-
对于操作 2,计算 \(sum=\sum_{i=l_0}^{r_0} a_i\),然后按题意给 \([l_1,r_1]\) 赋值。
-
对于操作 3,按区间顺序遍历即可。
给出后两个操作的实现。
void modify(int x, int y, int l, int r)
{
auto ity = split(y + 1), itx = split(x);
int sum = 0;
for (auto it = itx; it != ity; it++)
if (it->v)
sum += it->r - it->l + 1;
assign(x, y, 0);
auto itr = split(r + 1), itl = split(l);
for (auto it = itl; it != itr; it++)
{
if (it->v == 0)
{
if (sum >= it->r - it->l + 1)
{
it->v = 1;
sum -= it->r - it->l + 1;
}
else
{
assign(it->l, it->l + sum - 1, 1);
sum = 0;
}
}
}
}
int query(int l, int r)
{
auto itr = split(r + 1), itl = split(l);
int cur = 0, ans = 0;
for (auto it = itl; it != itr; it++)
{
if (it->v == 0)
cur += it->r - it->l + 1;
else
{
ans = max(ans, cur);
cur = 0;
}
}
return max(ans, cur);
}
然后发现会被卡超时。
我们考虑如何卡掉珂朵莉树,一定是反复遍历足够多的不同区间,在这道题中体现为对一个形如 010101010101 的序列反复进行查询,于是我们猜测,在 hack 数据中答案可能是 \(1\)。
于是我们写一个卡时,在程序快要超时的时候不再执行任何修改操作,对于查询直接输出 \(1\)。
然后就过了。
维护一个序列,每个节点有数字和颜色,你需要支持以下操作:
单点修改数字
区间推平颜色
求区间全颜色段最小和
求区间不重复颜色段最大和
保证数据随机。
由于数字和颜色之间没有直接关系,我们考虑使用不同的数据结构。
于是对于数字开一颗线段树,对于颜色使用珂朵莉树维护。
这样操作 1 和 2 就做完了。
对于操作 3,我们使用双指针,不断向右扩展右端点,直到包含所有颜色后更新答案,随后收缩左端点。
操作 4 的思路基本相同,但是改为不断移动左端点使右端点颜色出现次数始终为 1。
注意到这样会被 hack 掉,我们的操作 1 需要特判 \(c=1\) 的情况。
然后我们可以写出一份如下的代码:
#include <bits/stdc++.h>
using namespace std;
#define root 1, n, 1
#define lson s, mid, p * 2
#define rson mid + 1, t, p * 2 + 1
#define endl '\n'
#define int long long
const int maxn = 100'000 + 10;
const int maxc = 100 + 10;
const int inf = 0x3f3f3f3f;
int n, m, c;
int a[maxn], b[maxn];
int tot, cnt[maxc];
struct node
{
int l, r;
mutable int v;
node(int L, int R = -1, int v = 0) : l(L), r(R), v(v) {}
bool operator<(const node &o) const { return l < o.l; }
};
set<node> s;
auto split(int pos)
{
auto it = s.lower_bound(node(pos, -1, 0));
if (it != s.end() && it->l == pos)
return it;
it--;
int L = it->l, R = it->r, V = it->v;
s.erase(it);
s.insert(node(L, pos - 1, V));
return s.insert(node(pos, R, V)).first;
}
void assign(int l, int r, int v)
{
auto itr = split(r + 1), itl = split(l);
s.erase(itl, itr);
s.insert(node(l, r, v));
}
int sum(int l, int r)
{
auto itr = split(r + 1), itl = split(l);
int res = 0;
for (auto it = itl; it != itr; it++)
res = (res + (it->r - it->l + 1) * it->v);
return res;
}
struct Tree
{
int sum, tag;
int mx, mn;
} tr[maxn * 4];
void push_up(int p)
{
tr[p].sum = tr[p * 2].sum + tr[p * 2 + 1].sum;
tr[p].mx = max(tr[p * 2].mx, tr[p * 2 + 1].mx);
tr[p].mn = min(tr[p * 2].mn, tr[p * 2 + 1].mn);
}
void push_down(int s, int t, int p)
{
int mid = (s + t) / 2;
if (s != t && tr[p].tag)
{
tr[p * 2].tag = tr[p].tag;
tr[p * 2 + 1].tag = tr[p].tag;
tr[p * 2].sum = tr[p].tag * (mid - s + 1);
tr[p * 2 + 1].sum = tr[p].tag * (t - mid);
tr[p * 2].mx = tr[p * 2].mn = tr[p].tag;
tr[p * 2 + 1].mx = tr[p * 2 + 1].mn = tr[p].tag;
tr[p].tag = 0;
}
}
void build(int s, int t, int p)
{
if (s == t)
{
tr[p].sum = a[s];
tr[p].mx = tr[p].mn = a[s];
return;
}
int mid = (s + t) / 2;
build(lson);
build(rson);
push_up(p);
}
void range_modify(int L, int R, int VAL, int s, int t, int p)
{
if (L <= s && t <= R)
{
tr[p].tag = VAL;
tr[p].sum = VAL * (t - s + 1);
tr[p].mx = tr[p].mn = VAL;
return;
}
push_down(s, t, p);
int mid = (s + t) / 2;
if (L <= mid)
range_modify(L, R, VAL, lson);
if (R > mid)
range_modify(L, R, VAL, rson);
push_up(p);
}
int range_sum(int L, int R, int s, int t, int p)
{
if (L <= s && t <= R)
{
return tr[p].sum;
}
push_down(s, t, p);
int mid = (s + t) / 2, sum = 0;
if (L <= mid)
sum += range_sum(L, R, lson);
if (R > mid)
sum += range_sum(L, R, rson);
return sum;
}
int range_max(int L, int R, int s, int t, int p)
{
if (L <= s && t <= R)
return tr[p].mx;
push_down(s, t, p);
int mid = (s + t) / 2, res = LLONG_MIN;
if (L <= mid)
res = max(res, range_max(L, R, lson));
if (R > mid)
res = max(res, range_max(L, R, rson));
return res;
}
int range_min(int L, int R, int s, int t, int p)
{
if (L <= s && t <= R)
return tr[p].mn;
push_down(s, t, p);
int mid = (s + t) / 2, res = LLONG_MAX;
if (L <= mid)
res = min(res, range_min(L, R, lson));
if (R > mid)
res = min(res, range_min(L, R, rson));
return res;
}
void point_modify(int pos, int val, int s, int t, int p)
{
if (s == t)
{
tr[p].sum = tr[p].mx = tr[p].mn = val;
return;
}
push_down(s, t, p);
int mid = (s + t) / 2;
if (pos <= mid)
point_modify(pos, val, lson);
else
point_modify(pos, val, rson);
push_up(p);
}
void add(int x)
{
if (!cnt[x]++)
tot++;
}
void del(int x)
{
if (!--cnt[x])
tot--;
}
int query1(int l, int r)
{
tot = 0;
memset(cnt, 0, sizeof cnt);
if (c == 1)
return range_min(l, r, root);
auto itr = split(r + 1), itl = split(l), it = itl;
int res = inf;
while (it != itr)
{
add(it->v);
while (tot == c)
{
int sum = range_sum(itl->r, it->l, root);
res = min(res, sum);
del((itl++)->v);
}
it++;
}
return (res == inf) ? -1 : res;
}
int query2(int l, int r)
{
tot = 0;
memset(cnt, 0, sizeof cnt);
auto itr = split(r + 1), itl = split(l), it = itl;
int res = range_max(l, r, root);
while (it != itr)
{
add(it->v);
while (it != itl && cnt[it->v] > 1)
del((itl++)->v);
if (it != itl)
{
int sum = range_sum(itl->r, it->l, root);
res = max(res, sum);
}
if (it->l != it->r)
while (it != itl)
del((itl++)->v);
it++;
}
return res;
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
cin >> n >> m >> c;
for (int i = 1; i <= n; i++)
cin >> a[i];
for (int i = 1; i <= n; i++)
{
cin >> b[i];
s.insert(node(i, i, b[i]));
}
build(root);
while (m--)
{
int op, l, r, x, y;
cin >> op;
if (op == 1)
{
cin >> x >> y;
range_modify(x, x, y, root);
}
else if (op == 2)
{
cin >> l >> r >> y;
assign(l, r, y);
}
else if (op == 3)
{
cin >> l >> r;
cout << query1(l, r) << endl;
}
else
{
cin >> l >> r;
cout << query2(l, r) << endl;
}
}
return 0;
}
简洁明了,就是太慢了,足足跑了 1.12s。
注意到珂朵莉树的底层实现可以使用链表,大大降低代码常数。我们再加上一些胡乱卡常优化,就可以得到:
#include <bits/stdc++.h>
using namespace std;
#define ROOT 1, n, 1
#define lson s, mid, p * 2
#define rson mid + 1, t, p * 2 + 1
#define endl '\n'
namespace IO
{
const int Size = (1 << 20) + 1;
char In[Size], *ss = In, *tt = In;
char Out[Size];
int Outp = 0;
#define getchar_fast() (tt == ss && (tt = (ss = In) + fread(In, 1, Size, stdin), ss == tt) ? EOF : *ss++)
inline int read()
{
int x = 0, c = getchar_fast(), f = 0;
while (c < '0' || c > '9')
{
if (c == '-')
f = 1;
c = getchar_fast();
}
while (c >= '0' && c <= '9')
x = x * 10 + (c ^ 48), c = getchar_fast();
return f ? -x : x;
}
inline void write(int x)
{
static char buf[20];
int p = 0;
if (x < 0)
Out[Outp++] = '-', x = -x;
if (x == 0)
Out[Outp++] = '0';
else
{
while (x)
buf[p++] = x % 10 + '0', x /= 10;
while (p)
Out[Outp++] = buf[--p];
}
}
inline void flush()
{
fwrite(Out, 1, Outp, stdout);
Outp = 0;
}
}
const int maxn = 100000 + 10;
const int maxc = 100 + 10;
const int inf = 0x3f3f3f3f;
int n, m, c;
int a[maxn];
short b[maxn];
short tot, cnt[maxc], vis[maxc], now = 1;
struct node
{
int l, r;
mutable int v;
node(int L, int R = -1, int v = 0) : l(L), r(R), v(v) {}
bool operator<(const node &o) const { return l < o.l; }
};
struct Block
{
Block *next;
int l, r, val;
Block(Block *next, int l, int r, int val)
: next(next), l(l), r(r), val(val) {}
} *root;
void init_block()
{
Block *last = nullptr;
for (int i = n; i >= 1; --i)
{
root = new Block(last, i, i, b[i]);
last = root;
}
}
Block *split(int pos)
{
for (Block *b = root; b; b = b->next)
{
if (b->l == pos)
return b;
if (b->l < pos && pos <= b->r)
{
b->next = new Block(b->next, pos, b->r, b->val);
b->r = pos - 1;
return b->next;
}
}
return nullptr;
}
void assign(int l, int r, int val)
{
Block *rb = split(r + 1);
Block *lb = split(l);
Block *b = lb;
while (b != rb)
{
Block *tmp = b->next;
delete b;
b = tmp;
}
Block *newb = new Block(rb, l, r, val);
if (lb == root)
root = newb;
else
{
Block *pre = root;
while (pre->next != lb)
pre = pre->next;
pre->next = newb;
}
}
struct Tree
{
int sum, tag;
int mx, mn;
} tr[maxn * 4];
#define push_up(p) \
tr[p].sum = tr[p * 2].sum + tr[p * 2 + 1].sum, \
tr[p].mx = max(tr[p * 2].mx, tr[p * 2 + 1].mx), \
tr[p].mn = min(tr[p * 2].mn, tr[p * 2 + 1].mn)
#define push_down(s, t, p) \
do \
{ \
int mid = ((s) + (t)) >> 1; \
if ((s) != (t) && tr[p].tag) \
{ \
tr[p * 2].tag = tr[p].tag; \
tr[p * 2 + 1].tag = tr[p].tag; \
tr[p * 2].sum = tr[p].tag * (mid - (s) + 1); \
tr[p * 2 + 1].sum = tr[p].tag * ((t) - mid); \
tr[p * 2].mx = tr[p * 2].mn = tr[p].tag; \
tr[p * 2 + 1].mx = tr[p * 2 + 1].mn = tr[p].tag; \
tr[p].tag = 0; \
} \
} while (0)
inline void build(int s, int t, int p)
{
if (s == t)
{
tr[p].sum = a[s];
tr[p].mx = tr[p].mn = a[s];
return;
}
int mid = (s + t) >> 1;
build(s, mid, p * 2);
build(mid + 1, t, p * 2 + 1);
push_up(p);
}
inline void range_modify(int L, int R, int VAL, int s, int t, int p)
{
if (L <= s && t <= R)
{
tr[p].tag = VAL;
tr[p].sum = VAL * (t - s + 1);
tr[p].mx = tr[p].mn = VAL;
return;
}
push_down(s, t, p);
int mid = (s + t) >> 1;
if (L <= mid)
range_modify(L, R, VAL, s, mid, p * 2);
if (R > mid)
range_modify(L, R, VAL, mid + 1, t, p * 2 + 1);
push_up(p);
}
inline int range_sum(int L, int R, int s, int t, int p)
{
if (L <= s && t <= R)
return tr[p].sum;
push_down(s, t, p);
int mid = (s + t) >> 1, sum = 0;
if (L <= mid)
sum += range_sum(L, R, s, mid, p * 2);
if (R > mid)
sum += range_sum(L, R, mid + 1, t, p * 2 + 1);
return sum;
}
inline int range_max(int L, int R, int s, int t, int p)
{
if (L <= s && t <= R)
return tr[p].mx;
push_down(s, t, p);
int mid = (s + t) >> 1, res = INT_MIN;
if (L <= mid)
res = max(res, range_max(L, R, s, mid, p * 2));
if (R > mid)
res = max(res, range_max(L, R, mid + 1, t, p * 2 + 1));
return res;
}
inline int range_min(int L, int R, int s, int t, int p)
{
if (L <= s && t <= R)
return tr[p].mn;
push_down(s, t, p);
int mid = (s + t) >> 1, res = INT_MAX;
if (L <= mid)
res = min(res, range_min(L, R, s, mid, p * 2));
if (R > mid)
res = min(res, range_min(L, R, mid + 1, t, p * 2 + 1));
return res;
}
inline void point_modify(int pos, int val, int s, int t, int p)
{
if (s == t)
{
tr[p].sum = tr[p].mx = tr[p].mn = val;
return;
}
push_down(s, t, p);
int mid = (s + t) >> 1;
if (pos <= mid)
point_modify(pos, val, s, mid, p * 2);
else
point_modify(pos, val, mid + 1, t, p * 2 + 1);
push_up(p);
}
#define add(x) (vis[x] != now ? (vis[x] = now, cnt[x] = 1, ++tot) : (++cnt[x] == 1 ? ++tot : 0))
#define del(x) (--cnt[x] == 0 ? --tot : 0)
signed main()
{
n = IO::read(), m = IO::read(), c = IO::read();
for (int i = 1; i <= n; ++i)
a[i] = IO::read();
for (int i = 1; i <= n; ++i)
b[i] = IO::read();
build(ROOT);
init_block();
while (m--)
{
int op = IO::read();
if (op == 1)
{
int x = IO::read(), y = IO::read();
range_modify(x, x, y, ROOT);
}
else if (op == 2)
{
int l = IO::read(), r = IO::read(), y = IO::read();
assign(l, r, y);
}
else if (op == 3)
{
int l = IO::read(), r = IO::read();
tot = 0;
++now;
if (c == 1)
{
IO::write(range_min(l, r, ROOT));
IO::Out[IO::Outp++] = '\n';
continue;
}
Block *rb = split(r + 1), *lb = split(l), *itl = lb, *it = lb;
int res = inf;
while (it != rb)
{
add(it->val);
while (tot == c)
{
int sum = range_sum(itl->r, it->l, ROOT);
res = min(res, sum);
del(itl->val);
itl = itl->next;
}
it = it->next;
}
IO::write(res == inf ? -1 : res);
IO::Out[IO::Outp++] = '\n';
}
else
{
int l = IO::read(), r = IO::read();
tot = 0;
++now;
Block *rb = split(r + 1), *lb = split(l), *itl = lb, *it = lb;
int res = range_max(l, r, ROOT);
while (it != rb)
{
add(it->val);
while (it != itl && cnt[it->val] > 1)
{
del(itl->val);
itl = itl->next;
}
if (it != itl)
{
int sum = range_sum(itl->r, it->l, ROOT);
res = max(res, sum);
}
if (it->l != it->r)
while (it != itl)
{
del(itl->val);
itl = itl->next;
}
it = it->next;
}
IO::write(res);
IO::Out[IO::Outp++] = '\n';
}
}
IO::flush();
return 0;
}
//Powered by Github Copilot

总用时仅 347ms,暂时拿下最优解。

浙公网安备 33010602011771号