Loading

学习笔记 珂朵莉树/颜色段均摊

新坑。属实罕见。

这次从基础理论讲起。


简介

因为某人不是珂学家所以这里不介绍珂朵莉是什么。

珂朵莉树又名老司机树(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 的解释。

  1. std::set::erase 方法将使指向被擦除元素的引用和迭代器失效。而其他引用和迭代器不受影响。

  2. std::set::insert 方法不会使任何迭代器或引用失效。

  3. 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;
}

习题

来看几个习题:

Luogu P1840

数轴上有 \(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;
}

CF896C

珂朵莉树的起源题。

写一个数据结构支持如下操作:

  1. 区间加
  2. 区间赋值
  3. 区间第 \(k\) 小(数字大小相同算多次)
  4. 区间 \(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;
}

Luogu P2824

给定一个序列,\(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;
}

Luogu P4344

建议去看原题面。

  • 对于操作 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\)

然后就过了。

Luogu P5251

维护一个序列,每个节点有数字和颜色,你需要支持以下操作:

  1. 单点修改数字

  2. 区间推平颜色

  3. 求区间全颜色段最小和

  4. 求区间不重复颜色段最大和

保证数据随机。

由于数字和颜色之间没有直接关系,我们考虑使用不同的数据结构。

于是对于数字开一颗线段树,对于颜色使用珂朵莉树维护。

这样操作 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,暂时拿下最优解。

posted @ 2025-05-04 15:50  Merlin_Meow  阅读(92)  评论(1)    收藏  举报
Sakana Widget 自定义角色自适应示例