ODT 珂朵莉樹 詳解


人們都說珂教興國,無奈珂學家里數我最菜,只會背一背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 ::iterator,bool>,bool標志着插入是否成功,而iterator代表插入的位置,若key_value已經在set中,則iterator表示的key_value在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 ::iterator

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可以過

P3740 [HAOI2014]貼海報

CF915E Physical Education Lessons這個用ODT可以過

P4979 礦洞:坍塌

P4344 [SHOI2015]腦洞治療儀

P2572 [SCOI2010]序列操作

P5350 序列 題解 這個用ODT可以過

P2824 [HEOI2016/TJOI2016]排序 題解 這個用ODT也可以過,還挺快(開O2),沒有被卡主要還是因為沒有人想得到這題還能用ODT...


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM