珂朵莉树

珂朵莉树

1. 算法分析

玄学原理
    用一个三元组(l, r, val)记录一个区间,set来存储。每次区间赋值操作可以把一段区间[l, r]快速赋值为一个值,把[l, r]这段区间内的所有子区间合并为一个区间{l, r, x}。由于数据随机,[l, r]的期望长度为$ \frac{ 1 }{ 3 } (r - l + 1) , 即 每 次 赋 值 操 作 会 使 得 s e t 的 区 间 个 数 变 为 原 来 的 ,即每次赋值操作会使得set的区间个数变为原来的 使set \frac{ 2 }{ 3 } $,这样使得set的大小快速下降,最后趋于logn个区间。认为每次操作时间复杂度为O(logn)(也就是说set内只有log个区间,所以每次都暴力处理时间也只是logn)

使用情景
    当存在区间赋值的时候

2. 板子

/*
1 l r x: [l, r]内每个元素加上x
2 l r x: [l, r]内每个元素赋值为x
3 l r x: [l, r]的区间第x大
4 l r x y: [l, r]的每个元素的x次幂%y的和(指数和)
*/

#include <bits/stdc++.h>
using namespace std;

typedef long long LL;
const int MOD7 = 1e9 + 7;
const int MOD9 = 1e9 + 9;
const int N = 1e5 + 7;

//快速幂
LL qmi(LL a, LL b, LL mod) {
    LL res = 1;
    LL ans = a % mod;
    while (b) {
        if (b & 1) res = res * ans % mod;
        ans = ans * ans % mod;
        b >>= 1;
    }
    return res;
}

// 表示[l,r]这一个区间中所有的数都是v
struct node {
    int l, r;
    mutable LL v;
    node(int L, int R = -1, LL V = 0) : l(L), r(R), v(V) {}
    bool operator<(const node& o) const { return l < o.l; }
};

set<node> s;

// 拆分操作
// 一个集合中,有一部分需要修改,而另一部分不需要修改.把集合拆开,拆成两部分,要修改的就修改,不修改的就算了
// 找pos所在的三元组[l, r],然后切割为[l, pos - 1], [pos, r]
set<node>::iterator split(int pos) {
    auto it = s.lower_bound(node(pos));  //找到首个不小于pos的set
    if (it != s.end() && it->l == pos) return it;  //找到了,直接返回
    --it;                             //否则一定在前一个区间中
    if (pos > it->r) return s.end();  // 找不到,返回末尾迭代器的下一个位置
    int L = it->l, R = it->r;       //[l, r]就是要分裂的区间
    LL V = it->v;                   //取出值
    s.erase(it);                    //删除原集合
    s.insert(node(L, pos - 1, V));  //构建前半段的新结合
    return s.insert(node(pos, R, V)).first;  //构建后半段的新集合并且返回地址
}

//区间加操作
void add(int l, int r, LL val = 1) {
    split(l);
    auto itr = split(r + 1), itl = split(l);  // 得到区间左右端点迭代器
    for (; itl != itr; ++itl) itl->v += val;  // 暴力累加
}

//区间赋值操作
//珂朵莉树的复杂度是由assign_val保证的.由于数据随机,有1/4的操作为assign。vset的大小快速下降,最终趋于logn
//,使得这种看似暴力无比的数据结构复杂度接近mlogn 。
void assign_val(int l, int r, LL val = 0) {
    split(l);
    auto itr = split(r + 1), itl = split(l);  //求出要被摊平区间的收尾地址
    s.erase(itl, itr);                        //删除原集合
    s.insert(node(l, r, val));                //添加新集合
}

//求区间第K大值,reversed控制顺序与逆序
LL rank_(int l, int r, int k, bool reversed = 0) {
    vector<pair<LL, int>> vp;
    if (reversed) k = r - l + 2 - k;
    split(l);
    auto itr = split(r + 1), itl = split(l);  // 得到区间左右端点的迭代器
    vp.clear();
    for (; itl != itr; ++itl)  // 放入vector
        vp.push_back({itl->v, itl->r - itl->l + 1});
    sort(vp.begin(), vp.end());  // 从小到大排序
    for (auto i : vp)            // 得到第k大
    {
        k -= i.second;
        if (k <= 0) return i.first;
    }
    return -1LL;
}

//区间值求和操作
LL sum(int l, int r, int ex, int mod) {
    split(l);
    auto itr = split(r + 1), itl = split(l);  // 得到左右的迭代器
    LL res = 0;
    for (; itl != itr; ++itl)  // 暴力求和
        res = (res + (LL)(itl->r - itl->l + 1) * qmi(itl->v, LL(ex), LL(mod))) %
              mod;
    return res;
}

int n, m;
LL seed, vmax;

LL rnd() {
    LL ret = seed;
    seed = (seed * 7 + 13) % MOD7;
    return ret;
}

LL a[N];

int main() {
    cin >> n >> m >> seed >> vmax;
    for (int i = 1; i <= n; ++i) {
        a[i] = (rnd() % vmax) + 1;
        s.insert(node(i, i, a[i]));
    }
    s.insert(node(n + 1, n + 1, 0));
    int lines = 0;
    for (int i = 1; i <= m; ++i) {
        int op = int(rnd() % 4) + 1;
        int l = int(rnd() % n) + 1;
        int r = int(rnd() % n) + 1;
        if (l > r) swap(l, r);
        int x, y;
        if (op == 3)
            x = int(rnd() % (r - l + 1)) + 1;
        else
            x = int(rnd() % vmax) + 1;
        if (op == 4) y = int(rnd() % vmax) + 1;
        if (op == 1)
            add(l, r, LL(x));  // 区间增加x
        else if (op == 2)
            assign_val(l, r, LL(x));  // 区间赋值x
        else if (op == 3)
            cout << rank_(l, r, x) << endl;  // 求区间第x大
        else
            cout << sum(l, r, x, y) << endl;  // 区间指数和
    }
    return 0;
}

3. 例题

CF915E Physical Education Lessons
n个元素,黑白两种状态,起始全为黑色,区间黑白染色,每次操作结束后给出整个序列的黑色总数

/* 每个操作就是区间赋值0或1,顺带把总和修改一下 */
#include <bits/stdc++.h>
using namespace std;

typedef long long LL;
int n, q;
LL res;

// 表示[l,r]这一个区间中所有的数都是v
struct node {
    int l, r;
    mutable LL v;
    node(int L, int R = -1, LL V = 0) : l(L), r(R), v(V) {}
    bool operator<(const node& o) const { return l < o.l; }
};

set<node> s;

// 拆分操作
// 一个集合中,有一部分需要修改,而另一部分不需要修改.把集合拆开,拆成两部分,要修改的就修改,不修改的就算了
// 找pos所在的三元组[l, r],然后切割为[l, pos - 1], [pos, r]
set<node>::iterator split(int pos) {
    auto it = s.lower_bound(node(pos));  //找到首个不小于pos的set
    if (it != s.end() && it->l == pos) return it;  //找到了,直接返回
    --it;                             //否则一定在前一个区间中
    if (pos > it->r) return s.end();  // 找不到,返回末尾迭代器的下一个位置
    int L = it->l, R = it->r;       //[l, r]就是要分裂的区间
    LL V = it->v;                   //取出值
    s.erase(it);                    //删除原集合
    s.insert(node(L, pos - 1, V));  //构建前半段的新结合
    return s.insert(node(pos, R, V)).first;  //构建后半段的新集合并且返回地址
}

//区间赋值操作
//珂朵莉树的复杂度是由assign_val保证的.由于数据随机,有1/4的操作为assign。vset的大小快速下降,最终趋于logn
//,使得这种看似暴力无比的数据结构复杂度接近mlogn 。
void assign_val(int l, int r, bool val) {
    auto itr = split(r + 1), itl = split(l), it = itl;
    for (; itl != itr; ++itl) {  // 每次操作的时候顺便维护下res的值
        if (val == 1 && itl->v == 0) res += itl->r - itl->l + 1;
        if (val == 0 && itl->v == 1) res -= itl->r - itl->l + 1;
    }
    s.erase(it, itr);
    s.insert(node(l, r, val));
}

int main() {
    cin >> n >> q;
    res = n;
    s.insert(node(1, n, 1));
    while (q--) {
        int op, l, r;
        scanf("%d%d%d", &l, &r, &op);
        if (op == 1)
            assign_val(l, r, 0);  // 区间赋值为0
        else
            assign_val(l, r, 1);  // 区间赋值为1
        printf("%lld\n", res);    // 打印1的数目
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值