珂朵莉树
珂朵莉树,简称 \(\text{odt}\),又称老司机树,用于区间推平或数据随机。其思想暴力,实现简单,名字好听,深受 \(\text{OIer}\) 喜爱。
CF896C Willem, Chtholly and Seniorious
区间推平会使最后的序列变成一段一段连续的数,那我们记一个结构体,保存每一段的端点极其权值,按左端点从小到大排序。
珂朵莉树核心是 split 和 assign 函数,用来分裂和合并。
split(x) 的意思是以 \(x\) 做切割,找到一个含有 \(x\) 的区间 \([l, r]\),将其分裂为 \([l, x - 1]\) 和 \([x, r]\)。如果 \(x\) 本身就是一个区间的开头,就不用切割了,直接返回这个区间。
assign 则是合并。我们可以把整个 \(\text{set}\) 中所有要被合并掉的都删掉,然后插入一个新区间表示推平以后的结果。
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1e5 + 100;
const int Mod = 1000000007;
int n, m, seed, vmax, a[N];
struct odt {
int l, r;
mutable int v;
odt(int l, int r = 0, int v = 0) : l(l), r(r), v(v) {}
const bool operator < (const odt &e) const {
return l < e.l;
}
};
set<odt> s;
set<odt>::iterator split(int x) {
auto it = s.lower_bound(odt(x));
if (it != s.end() && it -> l == x) return it;
it--; if (it -> r < x) return s.end();
int l = it -> l, r = it -> r, v = it -> v;
s.erase(it), s.insert(odt(l, x - 1, v));
return s.insert(odt(x, r, v)).first;
}
void assign(int l, int r, int x) {
set<odt>::iterator itr = split(r + 1), itl = split(l);
s.erase(itl, itr), s.insert(odt(l, r, x));
}
void add(int l, int r, int x) {
set<odt>::iterator itr = split(r + 1), itl = split(l);
for (auto it = itl; it != itr; it++) it -> v += x;
}
int qpow(int a, int b, int mod) {
if (!b) return 1;
int tmp = qpow(a, b / 2, mod);
if (b & 1) return tmp % mod * tmp % mod * a % mod;
return tmp % mod * tmp % mod;
}
struct Node {
int num, cnt;
bool operator < (const Node &e) const {
return num < e.num;
}
Node(int num, int cnt) : num(num), cnt(cnt) {}
};
int rnk(int l, int r, int x) {
set<odt>::iterator itr = split(r + 1), itl = split(l);
vector<Node> v;
for (auto it = itl; it != itr; it++) {
v.push_back(Node(it -> v, it -> r - it -> l + 1));
}
sort(v.begin(), v.end());
int i;
for (i = 0; i < v.size(); i++) {
if (v[i].cnt < x) x -= v[i].cnt;
else break;
}
return v[i].num;
}
int getsum(int l, int r, int x, int y) {
set<odt>::iterator itr = split(r + 1), itl = split(l);
int ans = 0;
for (auto it = itl; it != itr; it++) {
ans = (ans + qpow(it -> v % y, x, y) * (it -> r - it -> l + 1) % y) % y;
}
return ans;
}
int rnd() {
int ret = seed;
seed = (seed * 7 + 13) % Mod;
return ret;
}
signed main() {
cin >> n >> m >> seed >> vmax;
for (int i = 1; i <= n; i++) {
a[i] = (rnd() % vmax) + 1;
s.insert(odt(i, i, a[i]));
}
for (int i = 1, x, y; i <= m; i++) {
int opt = (rnd() % 4) + 1, l = (rnd() % n + 1), r = (rnd() % n + 1);
if (l > r) swap(l, r);
if (opt == 3) x = (rnd() % (r - l + 1)) + 1;
else x = (rnd() % vmax) + 1;
if (opt == 4) y = (rnd() % vmax) + 1;
if (opt == 1) add(l, r, x);
if (opt == 2) assign(l, r, x);
if (opt == 3) cout << rnk(l, r, x) << '\n';
if (opt == 4) cout << getsum(l, r, x, y) << '\n';
}
return 0;
}
看到区间推平,果断珂朵莉。
对于一段长为 \(x\) 的极长连续段,对于答案的贡献是 \(\max(0, x - k + 1)\),线段树维护即可。

浙公网安备 33010602011771号