题解 CF896C 【Willem, Chtholly and Seniorious】

灵茶山艾府

2019-10-29 22:32:13

Solution

## 基于链表实现的珂朵莉树 珂朵莉树实质上是一种可以维护区间上的分裂与合并的数据结构,但要求数据是随机的,或者有大量的随机合并操作,这样才能保证维护的区间个数是一个很小的值。 一开始,我们用不同的节点表示 $[1,1],[2,2],...,[n,n]$ 以及该区间上的值。 本题中的“把区间 $[l,r]$ 赋值为 $x$”对应着一个合并操作,若随机到的 $[l,r]$ 范围比较大,则意味着有大量的节点会合并成一个节点。经测试,在若干次随机合并后,**区间个数会骤降至一个稳定的范围**(大约几十个),这是理解珂朵莉树的关键。 图例:横轴为操作次数,纵轴为区间个数 ![](https://cdn.luogu.com.cn/upload/image_hosting/y5roe0d7.png) ### 数据定义 目前主流的实现是基于 `set` 来维护节点,但由于平均维护的区间个数很小,`set` 的优势并不明显。相比之下,链表(或数组)能更简洁地维护分裂与合并操作。 ```cpp typedef long long int64; struct Block { Block *next; // 链表下一节点 int l, r; // 区间范围 int64 val; // 区间上的值 Block(Block *next, int l, int r, int64 val): next(next), l(l), r(r), val(val) {} bool operator<(const Block &b) const { return val < b.val; } } *root; ``` ## 基本操作 ### 分裂区间 ```cpp // 返回左端点为 mid+1 的区间 Block *split(int mid) { for (Block *b = root; b; b = b->next) { // 遍历链表 if (b->l == mid + 1) { // 左端点为 mid+1 return b; } // 寻找能包含 mid 和 mid+1 的区间 [l, r],将其被拆分成 [l, mid] 和 [mid+1, r] if (b->l <= mid && mid + 1 <= b->r) { b->next = new Block(b->next, mid + 1, b->r, b->val); b->r = mid; return b->next; } } return nullptr; // 未找到,返回空 } ``` 在操作区间时,由于不能只维护区间的一部分,所以下面的操作进行之前都需要预先分裂区间,再完成相应操作。 ```cpp Block *lb, *rb; // 预分裂,保证后续操作在 [l, r] 内部 void prepare(int l, int r) { lb = split(l - 1); rb = split(r); } ``` ### 合并区间 ```cpp void merge(int l, int r, int64 val) { prepare(l, r); lb->r = r; // 将区间 [lb.l, lb.r] 修改成 [lb.l, r] lb->val = val; lb->next = rb; // 将 [lb.l, r] 链至其右侧相邻区间 } // 注:这里没有释放被删除节点的内存,若有需要可自行添加 ``` ### 区间修改与计算 遍历统计 $[l,r]$ 即可。 ```cpp // 区间更新 void add(int l, int r, int64 val) { prepare(l, r); for (Block *b = lb; b != rb; b = b->next) b->val += val; } // 区间第 k 小 int64 kth(int l, int r, int k) { prepare(l, r); vector<Block> blocks; for (Block *b = lb; b != rb; b = b->next) blocks.emplace_back(*b); sort(blocks.begin(), blocks.end()); k--; for (Block b: blocks) { int cnt = b.r - b.l + 1; if (k >= cnt) k -= cnt; else return b.val; } } // 快速幂 int64 quick_pow(int64 x, int n, int64 mod) { x %= mod; int64 res = 1 % mod; for (; n; n >>= 1) { if (n & 1) res = res * x % mod; x = x * x % mod; } return res; } // 区间幂和 int64 pow_sum(int l, int r, int n, int64 mod) { prepare(l, r); int64 sum = 0; for (Block *b = lb; b != rb; b = b->next) sum += int64(b->r - b->l + 1) * quick_pow(b->val, n, mod); return sum % mod; } ``` ## AC 代码 ```cpp #include <iostream> #include <vector> #include <algorithm> using namespace std; typedef long long int64; int64 seed; // 生成 [0, n-1] 的随机数 int rand(int n) { int64 ret = seed; seed = (seed * 7 + 13) % int64(1e9 + 7); return int(ret) % n; } struct Block { Block *next; int l, r; int64 val; Block(Block *next, int l, int r, int64 val): next(next), l(l), r(r), val(val) {} bool operator<(const Block &b) const { return val < b.val; } } *root, *lb, *rb; Block *split(int mid) { for (Block *b = root; b; b = b->next) { if (b->l == mid + 1) { return b; } if (b->l <= mid && mid + 1 <= b->r) { b->next = new Block(b->next, mid + 1, b->r, b->val); b->r = mid; return b->next; } } return nullptr; } void prepare(int l, int r) { lb = split(l - 1); rb = split(r); } void add(int64 val) { for (Block *b = lb; b != rb; b = b->next) b->val += val; } void merge(int r, int64 val) { lb->r = r; lb->val = val; lb->next = rb; } int64 kth(int k) { vector<Block> blocks; for (Block *b = lb; b != rb; b = b->next) blocks.emplace_back(*b); sort(blocks.begin(), blocks.end()); k--; for (Block b: blocks) { int cnt = b.r - b.l + 1; if (k >= cnt) k -= cnt; else return b.val; } } int64 quick_pow(int64 x, int n, int64 mod) { x %= mod; int64 res = 1 % mod; for (; n; n >>= 1) { if (n & 1) res = res * x % mod; x = x * x % mod; } return res; } int64 pow_sum(int n, int64 mod) { int64 sum = 0; for (Block *b = lb; b != rb; b = b->next) sum += int64(b->r - b->l + 1) * quick_pow(b->val, n, mod); return sum % mod; } int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int n, m, v_max; cin >> n >> m >> seed >> v_max; root = new Block(nullptr, 0, 0, rand(v_max) + 1); Block *b = root; for (int i = 1; i < n; i++) { b->next = new Block(nullptr, i, i, rand(v_max) + 1); b = b->next; } for (; m; m--) { int op = rand(4) + 1; int l = rand(n), r = rand(n); if (l > r) swap(l, r); int x = op == 3 ? rand(r - l + 1) + 1 : rand(v_max) + 1; prepare(l, r); // 所有操作前预分裂区间 switch (op) { case 1: add(x); break; case 2: merge(r, x); break; case 3: cout << kth(x) << '\n'; break; default: int64 y = rand(v_max) + 1; cout << pow_sum(x, y) << '\n'; } } return 0; } ```