1. 程式人生 > >騙分導論--ODT珂朵莉樹

騙分導論--ODT珂朵莉樹

珂朵莉樹の由來

由於其騙分暴力的非正統演算法思想 雖然很多時候在隨機資料下跑時不錯 但切記這只是騙分暴力時間複雜度上並不正確

什麼時候用珂朵莉樹

珂朵莉樹一般用來解決本來應當由線段樹解決的區間類問題

而使得珂朵莉樹暴力艹標程的關鍵是assign區間推平操作 也就是題目中必須有區間賦值操作,而且資料純隨機的情況下才有複雜度保證

珂朵莉樹–初始化

一般珂朵莉樹選擇使用 set 維護數列 set中每個結點維護三個值(ll,rr,val)(ll,rr,val),意為[ll,rr][ll,rr]內的值都為valval 也就是每個節點儲存一段連續的值相同的區間

set中以每個區間左端點排序

struct node
{
    int ll,rr;
    mutable int val;
    node(int L,int R=-1,int V=0): ll(L), rr(R), val(V) {}
    bool operator < (const node& tt)const { return ll<tt.ll;}//以區間左端點排序
};
set<node> st;

需要注意的是mutablemutable 沒有它對valval的修飾,在接下來add函式裡導致CE。

接下來為方便有關迭代器的操作,我們做一個巨集定義

#define IT set<node>::iterator

珂朵莉樹–分裂Split

split(pos)split(pos)操作是指將原來含有pos位置的節點分成兩部分[l,pos1][l,pos-1][pos,r][pos,r] 並返回分裂後以pospos為左端點的結點的迭代器

IT split(int pos)
{
    IT it=st.lower_bound(node(pos));//二分找到第一個左端點不小於pos的區間
    if(it!=st.end()&&it->ll==pos)
return it;//pos本身就是某個區間的左端點,不用分裂 --it;//否則上一個區間才是包含pos的區間 int ll=it->ll,rr=it->rr,val=it->val; st.erase(it);//刪除原結點 st.insert(node(ll,pos-1,val)); return st.insert(node(pos,rr,val)).first;//這裡.first返回的是迭代器 }

珂朵莉樹–區間賦值Assign

珂朵莉樹就是靠這個東西維持其不正確的複雜度的

void assign(int ll,int rr,int val)
{
    IT itr=split(rr+1),itl=split(ll);
    st.erase(itl,itr);
    st.insert(node(ll,rr,val));
}

分裂出需要的區間,刪除後重新插入一個新的 注意分裂出[ll,rr][ll,rr]區間時要先分裂右端點,在分裂左端點

eraseerase方法可以刪除迭代器描述的區間[first,last)[first,last),注意左閉右開

void erase (iterator first, iterator last)

資料純隨機的情況下,可以證明每次assignassign區間長度期望N/3N/3 於是setset規模迅速下降,隨後達到接近O(mlogn)O(mlogn)玄學非正確複雜度

珂朵莉樹–其他暴力操作

其他操作真的就是規規矩矩的純暴力 直接取出對應區間,暴力對每一個進行操作

一般就是長這樣

void fun(int ll,int rr)
{
    IT itr=split(rr+1),itl=split(ll);
    for(;itl!=itr;++itl) 
    {
    	//...
    }
}
煮個栗子–區間求和
int qsum(int ll,int rr)
{
    int res=0;
    IT itr=split(rr+1),itl=split(ll);
    for(;itl!=itr;++itl) res+=(itl->rr-itl->ll+1)*itl->val;//注意乘(itl->rr-itl->ll+1)
    return res;
}
再煮個複雜點的栗子–區間Kth

也是一樣的暴力

#define pir pair<int,int>//前一個記錄值,後一個記錄出現次數
int kth(int ll,int rr,int k)
{
    vector<pir> vec;
    IT itr=split(rr+1),itl=split(ll);
    
    for(;itl!=itr;++itl)
    vec.push_back( pir(itl->val,itl->rr-itl->ll+1) );
    sort(vec.begin(),vec.end());//全部存下來排序就好
    
    for(vector<pir>::iterator it=vec.begin();it!=vec.end();++it)
    {
        k-=it->second;
        if(k<=0) return it->first;
    }
    return -1;
}

再次提醒

不管珂朵莉樹才實際中再怎麼優秀,那都只是資料隨機的結果 說到底只是暴力騙分手段 儘管可以用ODT AC很多題,但改變不了時間複雜度是非正確的的事實

建議平時的練習中少用,因為這樣會忽略了題目本身正解的精髓 考場上對題目沒有思路倒是一個很好的騙分手段