前言
關於珂朵莉
珂朵莉是世界上最幸福的女孩子,沒有之一,不接受任何反駁
\(\ \ \ \ \text{最幸福最幸福最最最幸福}\dots\text{的女孩子哦!}\)
上圖左珂朵莉·諾塔·瑟里歐尼斯,右威廉·克梅修
關於這個數據結構的名字
為了CF896C發明了這個算法,而這道題題面又與珂朵莉有關,故稱為珂朵莉樹,由於發明者的原因,也珂叫ODT(Old Driver Tree).
CF896C中文名:威廉,珂朵莉與瑟里歐尼斯
下面我們切入正題。
應用
解決各種線段樹無法完成的操作
注意珂朵莉樹保持復雜度主要依靠assign操作,所以題目中必須有區間賦值
還有很重要的一點:數據需純隨機
構造
用一個帶結構體的集合(std::set)維護序列
集合中的每個元素有左端點,右端點,值
下面展示該結構體的構造:
struct Node{
int l, r;
mutable int val;
Node(int a = -1, int b = -1, int c = 0){
l = a, r = b, val = c;
}
bool operator < (const Node &a){
return l < a.l;
}
};
*mutale,意為可變的,即不論在哪里都是可修改的,用於突破C++帶const函數的限制。
關鍵操作
Split
set
將原來含有pos的區間分為
\([l,pos)\)和
\([pos,r]\)兩段。
返回一個std::set的
迭代器,指向
\([pos,r]\)段
代碼
set<Node>::iterator split(int pos){
set<Node>::iterator it = st.lower_bound(Node(pos));
if (it != st.end() && it->l == pos) return it;
--it; Node tmp = *it; st.erase(it);
st.insert(Node(tmp.l, pos - 1, tmp.val));
return st.insert(Node(pos, tmp.r, tmp.val)).first; //first return iterator
}
Assign
注意:以后在使用split分裂區間的時候,請先右后左
區間賦值操作,也是珂樹維持其復雜度的關鍵函數
很暴力的思想,既然剛剛我們寫了一個split,那么就要把它用起來。
首先split出l並記返回值為itl,然后split出r+1並記返回值為itr,顯然我們要操作的區間為\([itl,itr)\),那么我們將\([itl,itr)\)刪除(std::set.erase(itl, itr)),再插入一個節點Node,其l為l,r為r,val為賦值的val。
我們注意到因為這個操作, \([itl,itr)\)中的所有節點合並為了一個節點,大大降低了集合的元素數量,因此調整了我們的復雜度
代碼(只有三行...)
void assign(int l, int r, long long val){
set<Node>::iterator itr = split(r + 1), itl = split(l);
st.erase(itl, itr);
st.insert((Node){l, r, val});
}
其他操作
通用方法是split出l,split出r+1,然后直接暴力掃描這段區間內的所有節點執行需要的操作
例如我們的區間和查詢:
long long querySum(int l, int r){
set<Node>::iterator itr = split(r + 1), itl = split(l); long long res = 0;
for (set<Node>::iterator it = itl; it != itr; ++it)
res += (it->r - it->l + 1) * it->val;
return res;
}
例如我們的區間加:
void add(int l, int r, long long val){
set<Node>::iterator itr = split(r + 1), itl = split(l);
for (set<Node>::iterator it = itl; it != itr; ++it)
it->val += val;
}
例如我們的區間第k小:
前置需要
algorithm庫中的std::sort(快速排序)
std::map(方便起見使用其中的pair),std::vector(方便起見)
還是split出l,split出r+1,然后將每個節點的值和個數(即r-l+1)組成一個pair(注意為了排序,將值放在第一關鍵字),將pair加入一個vector中
將vector排序
從vector的begin開始掃描,不停的使k減去vector當前項的第二關鍵字,若\(k \leq 0\),返回當前項的第一關鍵字
代碼實現
long long queryKth(int l, int r, int k){
vector< pair<int, int> > vec(0);
set<Node>::iterator itr = split(r + 1), itl = split(l);
for (set<Node>::iterator it = itl; it != itr; ++it)
vec.push_back(make_pair(it->val, it->r - it->l + 1));
sort(vec.begin(), vec.end());
for (vector< pair<int, int> >::iterator it = vec.begin(); it != vec.end(); ++it)
if ((k -= it->second) <= 0) return it->first;
return -1; //note:if there are negative numbers, return another impossible number.
}
代碼實現
#include <cstdio>
#include <vector>
#include <algorithm>
#include <set>
#include <map>
using namespace std;
//build
struct Node{
int l, r;
mutable long long val;
Node(int a = -1, int b = -1, long long c = 0){
l = a, r = b, val = c;
}
bool operator < (const Node &a) const{
return l < a.l;
}
};
set<Node> st;
//modify
set<Node>::iterator split(int pos){
set<Node>::iterator it = st.lower_bound(Node(pos));
if (it != st.end() && it->l == pos) return it;
--it; Node tmp = *it; st.erase(it);
st.insert(Node(tmp.l, pos - 1, tmp.val));
return st.insert(Node(pos, tmp.r, tmp.val)).first; //first return iterator
}
void assign(int l, int r, long long val){
set<Node>::iterator itr = split(r + 1), itl = split(l);
st.erase(itl, itr);
st.insert((Node){l, r, val});
}
void add(int l, int r, long long val){
set<Node>::iterator itr = split(r + 1), itl = split(l);
for (set<Node>::iterator it = itl; it != itr; ++it)
it->val += val;
}
//query
long long querySum(int l, int r){
set<Node>::iterator itr = split(r + 1), itl = split(l); long long res = 0;
for (set<Node>::iterator it = itl; it != itr; ++it)
res += (it->r - it->l + 1) * it->val;
return res;
}
long long queryKth(int l, int r, int k){
vector< pair<long long, int> > vec(0);
set<Node>::iterator itr = split(r + 1), itl = split(l);
for (set<Node>::iterator it = itl; it != itr; ++it)
vec.push_back(make_pair(it->val, it->r - it->l + 1));
sort(vec.begin(), vec.end());
for (vector< pair<long long, int> >::iterator it = vec.begin(); it != vec.end(); ++it)
if ((k -= it->second) <= 0) return it->first;
return -1; //note:if there are negative numbers, return another impossible number.
}
int main(){
return 0;
}
實際應用
CodeForces
896C Willem, Chtholly and Seniorious
珂樹算法發源地
數據還要隨機生成,非常毒瘤
好在操作中規中矩
吐槽:樣例錯了調了好久,發現快速冪打錯了,交了一次WA on test 3,然后發現vector忘開long long
代碼
#include <cstdio>
#include <vector>
#include <algorithm>
#include <set>
#include <map>
using namespace std;
long long read(){
long long x = 0; int zf = 1; char ch = ' ';
while (ch != '-' && (ch < '0' || ch > '9')) ch = getchar();
if (ch == '-') zf = -1, ch = getchar();
while (ch >= '0' && ch <= '9') x = x * 10 + ch - '0', ch = getchar(); return x * zf;
}
namespace Qpow{
long long pow(long long a, long long b, long long mod){
if (!a) return 0;
long long res = 1; a %= mod;
for ( ; b; (a *= a) %= mod, b >>= 1ll)
if (b & 1) (res *= a) %= mod;;
return res;
}
};
//build
struct Node{
int l, r;
mutable long long val;
Node(int a = -1, int b = -1, long long c = 0){
l = a, r = b, val = c;
}
bool operator < (const Node &a) const{
return l < a.l;
}
};
set<Node> st;
//modify
set<Node>::iterator split(int pos){
set<Node>::iterator it = st.lower_bound(Node(pos));
if (it != st.end() && it->l == pos) return it;
--it; Node tmp = *it; st.erase(it);
st.insert(Node(tmp.l, pos - 1, tmp.val));
return st.insert(Node(pos, tmp.r, tmp.val)).first; //first return iterator
}
void assign(int l, int r, long long val){
set<Node>::iterator itr = split(r + 1), itl = split(l);
st.erase(itl, itr);
st.insert((Node){l, r, val});
}
void add(int l, int r, long long val){
set<Node>::iterator itr = split(r + 1), itl = split(l);
for (set<Node>::iterator it = itl; it != itr; ++it)
it->val += val;
}
//query
long long querySum(int l, int r){
set<Node>::iterator itr = split(r + 1), itl = split(l); long long res = 0;
for (set<Node>::iterator it = itl; it != itr; ++it)
res += (it->r - it->l + 1) * it->val;
return res;
}
long long querySumWithPow(int l, int r, long long x, long long mod){
set<Node>::iterator itr = split(r + 1), itl = split(l); long long res = 0;
for (set<Node>::iterator it = itl; it != itr; ++it)
(res += (it->r - it->l + 1) * Qpow::pow(it->val, x, mod)) %= mod;
return res;
}
long long queryKth(int l, int r, int k){
vector< pair<long long, int> > vec(0);
set<Node>::iterator itr = split(r + 1), itl = split(l);
for (set<Node>::iterator it = itl; it != itr; ++it)
vec.push_back(make_pair(it->val, it->r - it->l + 1));
sort(vec.begin(), vec.end());
for (vector< pair<long long, int> >::iterator it = vec.begin(); it != vec.end(); ++it)
if ((k -= it->second) <= 0) return it->first;
return -1; //note:if there are negative numbers, return another impossible number.
}
long long seed;
long long a[100005];
inline long long rnd(){
long long ret = seed; seed = (seed * 7 + 13) % 1000000007;
return ret;
}
int main(){
int n = read(), m = read(); seed = read(); long long vmax = read();
for (int i = 1; i <= n; ++i){
a[i] = (rnd() % vmax) + 1;
st.insert((Node){i, i, a[i]});
}
long long x, y;
for (int i = 1; i <= m; ++i){
int op = (rnd() % 4) + 1, l = (rnd() % n) + 1, r = (rnd() % n) + 1;
if (l > r){int tmp = l; l = r, r = tmp;}
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("%I64d\n", queryKth(l, r, x));
else if (op == 4) printf("%I64d\n", querySumWithPow(l, r, x, y));
}
return 0;
}