人們都說珂教興國,無奈珂學家里數我最菜,只會背一背ODT板子
珂朵莉鎮樓
名字來源
ODT全稱Old Driver Tree,中文名 珂朵莉樹
有人為了CF896C發明了這個算法,這道題又和珂朵莉有關,所以這個算法叫做珂朵莉樹
另外,由於發明者(lxl)的原因,也珂叫ODT(Old Driver Tree).
Warning!
ODT可以解決一些線段樹不能解決的問題,如區間次冪求和
但要求數據隨機,隨機下跑得很快,開了O2更快
數據不隨機就是個假算法,開了O2也沒用
前置芝士:set
這個大佬講的很詳細
據說set是用紅黑樹實現的,O2下效率很高
set是自帶排序+去重的
set自帶一些函數
迭代器類似於指針
begin() 返回set容器的第一個元素
end() 返回set容器的最后一個元素的后一個
clear() 刪除set容器中的所有的元素
empty() 判斷set容器是否為空
size() 返回當前set容器中的元素個數
lower_bound() 返回指向大於或等於某值的第一個元素的迭代器
upper_bound() 返回大於某個值元素的迭代器
find() 返回一個指向被查找到元素的迭代器,如果沒找到則返回end()。
count() 返回查找set中某個某個鍵值出現的次數,結果只會是0/1
erase(iterator) 刪除定位器iterator指向的值
erase(key_value) 刪除鍵值key_value的值
insert(key_value) 將key_value插入到set中,返回值是 pair< set
最后一個看不懂沒關系,只要知道insert().first返回的是一個迭代器就行
另外,set的儲存元素是結構體時,需要重載"<"
像這樣
friend bool operator <(const node &a,const node &b){return a.l<b.l;} `
下面是我的結構體寫法
struct node
{
int l,r;
mutable long long val;
node(int L=0,int R=-1,int V=0):l(L),r(R),val(V){}
friend bool operator <(const node &a,const node &b){return a.l<b.l;}
};
算法流程
想看視頻講解的點這里
ODT主要是依靠set來實現的,用來維護一個序列
set里裝的是結構體元素,每個元素有3個基本屬性:L,R,val
就相當於把序列分為若干個"塊",每一個塊里的元素在序列上相連且權值相等
L和R是塊的左右端點,val是權值
借大佬的圖一用
``
關鍵操作
一般寫ODT時都有define IT set
split
將含有pos的區間[l,r]拆分為[l,pos-1)和[pos,r]
同時返回一個指向[pos,r]的迭代器
IT split(int pos)
{
IT it=s.lower_bound(node(pos));
if(it!=s.end()&&it->l==pos)return it;
--it;
int ll=it->l,lr=it->r;
char lv=it->v;
s.erase(it);
s.insert(node(ll,pos-1,lv));
return s.insert(node(pos,lr,lv)).first;
}
Assign/tuiping
極其暴力的一個操作
把原先的刪了,再重新加一個新的
注意split時,要先split(r+1),再 split(l),否則會RE,原因有點復雜,想知道的看這個
void tuiping(int l,int r,char v)
{
IT it2=split(r+1),it1=split(l);
s.erase(it1,it2);
s.insert(node(l,r,v));
}
其他操作
以CF896C為例,下面為了美觀自動省去了long long和取模操作
這些真的是一個比一個暴力
區間求x次冪和
線段樹無法完成此操作
用ODT的話暴力加起來就行
int sum(int l,int r,int x,int mod)
{
IT it2=split(r+1),it1=split(l);
long long res=0;
for(;it1!=it2;++it1)res=res+(it1->r-it1->l+1)*ksm(it1->val,x,mod);
return res;
}
區間加
暴力加就行
void add(int l,int r,int v)
{
IT it2=split(r+1),it1=split(l);
for(;it1!=it2;++it1)it1->val+=v;
}
區間賦值
Assign原封不動
區間k小
暴力取出來排個序就行
int my_rank(int l, int r, int k)
{
vector<pair<int, int> > vp;
IT itr = split(r+1),itl = split(l);
vp.clear();
for (; itl != itr; ++itl)
vp.push_back(pair<int,int>(itl->val, itl->r - itl->l + 1));
sort(vp.begin(), vp.end());
for (vector<pair<int,int> >::iterator it=vp.begin();it!=vp.end();++it)
{
k -= it->second;
if (k <= 0) return it->first;
}
}
然后這道題就解決啦
后記:
1.要想用ODT一定要看看數據是不是隨機的,有的時候出題人會給一部分隨機數據的部分分
2.寫ODT的時候要小心,寫錯很容易RE
題單:
下面的好多題都卡了ODT(但還有部分分),可以用來練練手
CF896C Willem, Chtholly and Seniorious這個用ODT可以過
CF915E Physical Education Lessons這個用ODT可以過
P2824 [HEOI2016/TJOI2016]排序 題解 這個用ODT也可以過,還挺快(開O2),沒有被卡主要還是因為沒有人想得到這題還能用ODT...