首页 > 其他分享 >珂朵莉树浅析

珂朵莉树浅析

时间:2022-12-29 15:34:17浏览次数:53  
标签:Node set int pos 朵莉树 split 区间 浅析

珂朵莉树浅析

0. 关于珂朵莉

中国珂学院

1. 珂朵莉树实现

珂朵莉树(也称 Old Driver Tree),是一种暴力数据结构,其代表的操作有:

  • 区间推平(将区间 \([l,r]\) 的数修改为 \(x\));
  • 区间次幂和(给定 \(x,y\),计算 \(\sum_{l\le i\le r} a_i^x \bmod y\))。

当然珂朵莉树也可以做到区间加,区间第 \(x\) 小等操作。

珂朵莉树适用于数据随机的情况,平均时间复杂度 \(O(m\log n)\)。

接下来以 CF896C Willem, Chtholly and Seniorious 为例,讲解珂朵莉树的实现方式。

1.1 初始化

珂朵莉树是以一个结构体 Node 为基础的,我们对于每一个区间,定义 \(l,r,v\),分别表示区间左端点,区间右端点和区间中的值。

struct Node {
    int l, r; // 左右端点
    mutable int v; // 区间中的值, mutable表示v虽然是常量,但是可修改

    Node (int l, int r = 0, int v = 0) : l(l), r(r), v(v) {} // 成员函数,创造一个为(l,r,v)的区间

    bool operator < (const Node &a) const { // 重载<运算符, 便于排序
        return l < a.l;
    }
};

接下来我们需要用一个 set \(s\) 来存储这些区间,由于我们重载了运算符,所以 \(s\) 中的区间会按左端点从小到大排序。

1.2 Split 操作

珂朵莉树的关键操作就是 split,它可以将区间 \([l,r]\) 划分为两个区间 \([l,pos-1]\) 和 \([pos,r]\)。

set<Node>::iterator split(int pos) { // 分割操作
    set<Node>::iterator it = s.lower_bound(Node(pos)); // 找到第一个左端点不小于pos的区间
    if (it != s.end() && it->l == pos) return it; // pos本身就是区间左端点,直接返回

    it --; 
    if (it->r < pos) return s.end(); //pos过大,超出了最后一个区间的范围
    int l = it->l, r = it->r, v = it->v;
    s.erase(it), s.insert(Node(l, pos-1, v)); // 将区间[l,r]分为两段[l,pos-1]和[pos,r]
    return s.insert(Node(pos, r, v)).first; // 返回后一个区间的迭代器
}

1.3 Assign 操作

珂朵莉树不仅需要划分操作,还需要合并操作 assign,它可以将 \([l,r]\) 合并为一个区间,并赋值为 \(x\)。

void assign(int l, int r, int x) { // 合并区间[l,r],并赋值为x
    set<Node>::iterator itr = split(r+1), itl = split(l); // itr为r所在的区间的后一个区间的迭代器, itl为l所在的区间的迭代器
    s.erase(itl, itr); // 删去itl~itr中的所有区间
    s.insert(Node(l, r, x)); // 插入一个区间, 左端点为l, 右端点为r, 区间值为x
}

1.4 其他操作

其他操作基本都和 assign 操作差不多。

区间和:

void add(int l, int r, int x) { // 区间加
    set<Node>::iterator itr = split(r+1), itl = split(l);
    for (set<Node>::iterator it = itl; it != itr; ++it) 
        it->v += x;
}

区间第 \(x\) 小:

struct Rank { // 定义一个结构体,存储每一个数的数值和数量
    int num, cnt;

    Rank (int num, int cnt) : num(num), cnt(cnt) {}

    bool operator < (const Rank &a) const {
        return num < a.num;
    }
};

int rnk(int l, int r, int x) { // 查询区间第x小
    set<Node>::iterator itr = split(r+1), itl = split(l);
    vector<Rank> v;
    for (set<Node>::iterator it = itl; it != itr; ++it) 
        v.push_back(Rank(it->v, it->r - it->l + 1)); // 一个区间[l,r]中, 有r-l+1个v, 将其插入vector
    sort(v.begin(), v.end()); // 将v中的区间按数值排序

    int i = 0;
    for ( ; i < v.size(); ++i) {
        if (v[i].cnt < x) {
            x -= v[i].cnt;
        } else {
            break;
        }
    }
    return v[i].num;
}

区间次幂和:

int pow(int a, int b, int p) {
    int ans = 1;
    a %= p;
    while (b) {
        if (b & 1) ans = ans * a % p;
        a = a * a % p;
        b >>= 1;
    }
    return ans;
}

int cal(int l, int r, int x, int y) { // 计算区间x次幂
    set<Node>::iterator itr = split(r+1), itl = split(l);
    int ans = 0;
    for (set<Node>::iterator it = itl; it != itr; ++it) 
        ans = (ans + pow(it->v, x, y)*(it->r - it->l + 1) % y) % y;
    return ans;        
}

1.5 完整代码

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <vector>
#include <set>

using namespace std;

#define int long long

const int mod = 1e9+7;
const int N = 1e5+10;

int n, m, seed, vmax;
int a[N];

int rnd() {
    int ret = seed;
    seed = (seed * 7ll + 13) % mod;
    return ret;
}

struct Node {
    int l, r; 
    mutable int v;

    Node (int l, int r = 0, int v = 0) : l(l), r(r), v(v) {}

    bool operator < (const Node &a) const {
        return l < a.l;
    }
};

set<Node> s;

set<Node>::iterator split(int pos) { // 分割操作
    set<Node>::iterator it = s.lower_bound(Node(pos));
    if (it != s.end() && it->l == pos) return it; // pos本身就是区间左端点

    it --;
    if (it->r < pos) return s.end(); //pos过大
    int l = it->l, r = it->r, v = it->v;
    s.erase(it), s.insert(Node(l, pos-1, v)); // 将区间[l,r]分为两段[l,pos-1]和[pos,r]
    return s.insert(Node(pos, r, v)).first; // 返回后一个区间的迭代器
}

void assign(int l, int r, int x) { // 合并两个区间,并赋值为x
    set<Node>::iterator itr = split(r+1), itl = split(l);
    s.erase(itl, itr);
    s.insert(Node(l, r, x));
}

void add(int l, int r, int x) { // 区间加
    set<Node>::iterator itr = split(r+1), itl = split(l);
    for (set<Node>::iterator it = itl; it != itr; ++it) 
        it->v += x;
}

struct Rank {
    int num, cnt;

    Rank (int num, int cnt) : num(num), cnt(cnt) {}

    bool operator < (const Rank &a) const {
        return num < a.num;
    }
};

int rnk(int l, int r, int x) { // 查询区间第x小
    set<Node>::iterator itr = split(r+1), itl = split(l);
    vector<Rank> v;
    for (set<Node>::iterator it = itl; it != itr; ++it) 
        v.push_back(Rank(it->v, it->r - it->l + 1));
    sort(v.begin(), v.end());

    int i = 0;
    for ( ; i < v.size(); ++i) {
        if (v[i].cnt < x) {
            x -= v[i].cnt;
        } else {
            break;
        }
    }
    return v[i].num;
}

int pow(int a, int b, int p) {
    int ans = 1;
    a %= p;
    while (b) {
        if (b & 1) ans = ans * a % p;
        a = a * a % p;
        b >>= 1;
    }
    return ans;
}

int cal(int l, int r, int x, int y) { // 计算区间x次幂
    set<Node>::iterator itr = split(r+1), itl = split(l);
    int ans = 0;
    for (set<Node>::iterator it = itl; it != itr; ++it) 
        ans = (ans + pow(it->v, x, y)*(it->r - it->l + 1) % y) % y;
    return ans;        
}

signed main() {
    scanf("%lld%lld%lld%lld", &n, &m, &seed, &vmax);
    for (int i = 1; i <= n; ++i) a[i] = (rnd() % vmax) + 1, s.insert(Node(i, i, a[i]));

    int op, l, r, x, y;
    for (int i = 1; i <= m; ++i) {
        op = (rnd() % 4) + 1, l = (rnd() % n) + 1, r = (rnd() % n) + 1;
        if (l > r) swap(l, r);
        if (op == 3) x = (rnd() % (r-l+1)) + 1;
        else x = (rnd() % vmax) + 1;
        if (op == 4) y = (rnd() % vmax) + 1;

        if (op == 1) {
            add(l, r, x);
        } else if (op == 2) {
            assign(l, r, x);
        } else if (op == 3) {
            printf("%lld\n", rnk(l, r, x));
        } else {
            printf("%lld\n", cal(l, r, x, y));
        }
    }
    system("pause");
    return 0;
}

2. 珂朵莉树例题

CF915E Physical Education Lessons

这一道题只需要用到 splitassign 函数,但是由于要求总和,所以要对 assign 进行一些修改。

struct Node {
    int l, r;
    mutable int v;

    Node (int l, int r = 0, int v = 0) : l(l), r(r), v(v) {}

    bool operator < (const Node &a) const {
        return l < a.l;
    }
};

set<Node> s;

set<Node>::iterator split(int pos) {
    set<Node>::iterator it = s.lower_bound(Node(pos));
    if (it != s.end() && it->l == pos) return it;

    it --;
    if (it->r < pos) return s.end();
    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 x) {
    set<Node>::iterator itr = split(r+1), itl = split(l);
    for (set<Node>::iterator it = itl; it != itr; ++it) 
        cnt -= it->v * (it->r - it-> l + 1);
    s.erase(itl, itr);
    s.insert(Node(l, r, x));
    cnt += x * (r-l+1);
}

int main() {
    scanf("%d%d", &n, &q);
    s.insert(Node(1, n, 1));

    int l, r, k; cnt = n;
    while (q -- ) {
        scanf("%d%d%d", &l, &r, &k);
        if (k == 1) assign(l, r, 0);
        else assign(l, r, 1);
        printf("%d\n", cnt);
    }
    return 0;
}

标签:Node,set,int,pos,朵莉树,split,区间,浅析
From: https://www.cnblogs.com/Jasper08/p/17012652.html

相关文章

  • 转贴:浅析 Linux 的国际化与本地化机制
       Linux是一个国际化的操作系统,它的工具集和设备驱动程序均支持多语言操作。本文通过分析glibc中实现国际化和本地化机制的函数和命令工具集以及从程序开发者、翻译......
  • 3.Containerd容器运行时的配置浅析与知识扩充实践
    公众号关注「WeiyiGeek」本章目录:0x00Containerd容器运行时配置指南如何配置Containerd的systemd守护进程服务?如何查看Containerd相关插件及其存目录?如何生成Con......
  • 浅析OpenCV中的BlobDetector
    ​​​​一、blob基础所谓Blob就是图像中一组具有某些共同属性(例如,灰度值)的连接像素。在上图中,深色连接区域是斑点,斑点检测的目的是识别并标记这些区域。OpenCV提供了一......
  • 视频x264编码浅析
    声明 x264_param_t 结构体变量:x264_param_t params;x264_param_default_preset(&params,"ultrafast","zerolatency");//优化编码延迟? 变量参数编码前赋值:......
  • 音频AAC编码浅析
    /**unsignedlongnSampleRate,//采样率,单位是bps*unsignedlongnChannels,//声道,1为单声道,2为双声道*unsignedlong&sampl......
  • 浅析容器运行时奥秘——OCI标准
    背景2013年Docker开源了容器镜像格式和运行时以后,为我们提供了一种更为轻量、灵活的“计算、网络、存储”资源虚拟化和管理的解决方案,在业界迅速火了起来。2014年更是容......
  • 领域驱动设计系列(2)浅析VO、DTO、DO、PO的概念、区别和用处
    PO:persistantobject持久对象最形象的理解就是一个PO就是数据库中的一条记录。好处是可以把一条记录作为一个对象处理,可以方便的转为其它对象。BO:businessobject业务对象主......
  • eBPF verifier常见错误浅析
    本文摘自毛文安公众号《酷玩BPF》文章,作者毛文安。​​收藏:eBPFverifier常见错误整理​​如今eBPF程序的编写,很多都是基于bcc或者bpftrace进行,也有开发者直接基于libbpf库......
  • 浅析Java中的final关键字
    谈到final关键字,想必很多人都不陌生,在使用匿名内部类的时候可能会经常用到final关键字。另外,Java中的String类就是一个final类,那么今天我们就来了解final这个关键字的用法。......
  • 分布式 | DBLE 新全局表检查实现浅析
    作者:孙正方爱可生DBLE核心研发成员,拥有丰富的分布式数据库中间件开发、咨询以及调优经验,擅长数据库中间件问题排查和处理,对线上中间件部分排错有深入的实践与认知。背景......