1. 程式人生 > >「學習筆記」珂朵莉樹 ODT

「學習筆記」珂朵莉樹 ODT

珂朵莉樹,也叫ODT(Old Driver Tree 老司機樹)

從前有一天,珂朵莉出現了。。。
然後有一天,珂朵莉樹出現了。。。

看看圖片的地址 Codeforces可還行)

沒錯,珂朵莉樹來自Codeforces 896C C. Willem, Chtholly and Seniorious

國外珂學家 滑稽)


前置芝士:
set的基本操作
迭代器(跟指標差不多
過載運算子、建構函式的簡單瞭解
mutable(下面也會講
暴力列舉
常數優化(inline O2 O3 register大法好啊

夠簡單了吧?除了真正的小白,大家都應該有所瞭解。


廢話完了,扯進正題(畢竟你不是珂學家,你是個O·I·E·R

)。

珂朵莉樹的適用範圍(缺一不可,不然複雜度就是不正確的,很容易被卡):

  1. 資料純隨機
  2. 有區間修改操作

大概就這兩個吧。珂朵莉樹畢竟是一種騙分演算法(珂朵莉:我不服),想到正解儘量用正解。


珂朵莉樹的主要思想就是用一個set來維護元素相同的區間。

這裡我們以P2572 [SCOI2010]序列操作為例,講一講珂朵莉樹。

先寫個結構體。

#define Re register //卡常操作 
struct node{
    int l, r; mutable bool val;
    node( int L, int R = -1, int v = 0 ):l(L), r(R), val(v){}//建構函式
    bool operator < ( const node t )const{ return l < t.l; }//過載運算子
};

l表示左邊界,r表示右邊界,val表示l~r儲存的值都是val(當然,根據題目需要,val的型別可以改變)。

mutable的作用很簡單。由於在set中,元素是以常量方式儲存的,不能直接修改。在set中我們是按l排序的,修改val的值實際上沒有關係,不會影響set中元素的順序,把val的型別前加個mutable,就可以直接修改val,否則還要刪除元素,再插入進去,降低了效率。因為珂朵莉樹比較暴力,我們要儘可能優化複雜度。

  1. 建立你的珂朵莉樹
    ls = 1; 
    for ( Re int i = 1; i <= N; ++i ) scanf( "%d", &a[i] );
    for ( Re int i = 2; i <= N; ++i ) if ( a[i] ^ a[i - 1] ) S.insert( node( ls, i - 1, a[i - 1] ) ), ls = i;
    S.insert( node( ls, N, a[N] ) );

直接把連續的一段段插進去即可。

舉個例子:

111001100011000

我們就會插入以下幾個元素(以 l、r、val順序

1 3 1
4 5 0
6 7 1
8 10 0
10 11 1
12 15 0

炒雞簡單對吧?

  1. Split

學過FHQ Treap的童鞋聽到這個很熟悉對吧?其實它們作用是差不多的,但是由於FHQ Treap是以二叉查詢樹結構儲存的,但這裡的珂朵莉樹直接用set存,相對來說簡單得多。

Split(pos)的作用就是在某個包含pos的區間[l,r]中,分成兩個區間[l,pos - 1],[pos,r]。實現很簡單,請看程式碼。

inline IT Split( Re int pos ){
    Re IT t(S.lower_bound(node(pos)));//找到左邊界第一個大於等於它的元素
    if ( t != S.end() && t->l == pos ) return t; // 如果左邊界就是這個元素,不用分了,直接返回[pos,r]也就是[l,r]
    t--;//前一個元素就是包含pos的區間
    Re int L(t->l), R(t->r); Re bool v(t->val);//存下來把原來的資訊
    S.erase(t);//刪了它!
    S.insert( node( L, pos - 1, v ) );//插入區間[l,pos - 1]
    return S.insert( node( pos, R, v ) ).first;//插入區間[pos,r]並返回[pos,r]的迭代器
}

舉例子:

如果把上面那個例子中,Split(2)
t 指向[4,5](4是第一個大於等於2的)
左邊界不是2,t--,指向區間[1,3]
分成兩個區間[1,1][2,3]
返回[2,3]的迭代器
  1. Assign

這個操作用於區間修改元素。由於這個操作可以迅速減少set中元素的個數,所以這是珂朵莉樹的複雜度保證。

也十分簡單,就是把邊界Split,中間全部刪除再插入一個元素就好了。

inline void Assign( Re int l, Re int r, Re bool v ){//把l到r所有元素統統變成v
    Re IT ed(Split(r + 1)), be(Split(l));//Split邊界 分成[...l-1] {[l...]...[..r]} [r+1...] be指向;[l...],ed指向[r+1...] 大括號中間全部要刪除
    S.erase( be, ed );//刪去be~ed-1的所有元素,就是大括號中間的部分
    S.insert(node( l, r, v ));//插入區間[l,r]
}

有一個小細節,要先執行Split(r+1),再執行Split(l)

為什麼呢?

舉反例——

還是拿建樹那裡的例子
Assign(2,2)
假設先執行Split(2)
第一個區間[1,3]變成了[1][2,3]
be指向區間[2,3]
再執行Split(3)時
[2,3]變成了[2][3]
ed指向[3]
然後如果呼叫了be
be原指向的區間[2,3]已經被刪除了
然後RE*8+TLE*1+AC*1

沒錯反過來的目的就是避免Split右區間時把be指向的區間刪了。

  1. 區間取反

暴力列舉即可(也要Split)

inline void Change( Re int l, Re int r ){
    Re IT ed(Split(r + 1)), be(Split(l));
    for ( Re IT it = be; it != ed; ++it ) it->val = !(it->val);
}
  1. 查詢1的個數

也很暴力,一個個列舉

inline int Get1( Re int l, Re int r ){
    Re IT ed(Split(r + 1)), be(Split(l)); Re int ans(0);
    for ( Re IT it = be; it != ed; ++it ) if ( it->val ) ans += (it->r) - (it->l) + 1;
    return ans;
}
  1. 查詢最長連續1的個數

還是暴力

inline int Get2( Re int l, Re int r ){
    Re IT ed(Split(r + 1)), be(Split(l)); Re int ans(0), cur(0);
    for ( Re IT it = be; it != ed; ++it )
        if ( it->val ) cur += (it->r) - (it->l) + 1;
        else ans = max( ans, cur ), cur = 0;
    ans = max( ans, cur );
    return ans;
}

差不多就這些了。

騙分大法好啊!

完整程式碼(https://www.luogu.org/problemnew/show/P2572)

#include<bits/stdc++.h>
using namespace std;
#define Re register 

struct node{
    int l, r; mutable bool val;
    node( int L, int R = -1, int v = 0 ):l(L), r(R), val(v){}
    bool operator < ( const node t )const{ return l < t.l; }
};

#define IT set<node>::iterator
set<node> S;

inline IT Split( Re int pos ){
    Re IT t(S.lower_bound(node(pos)));
    if ( t != S.end() && t->l == pos ) return t;
    t--;
    Re int L(t->l), R(t->r); Re bool v(t->val);
    S.erase(t);
    S.insert( node( L, pos - 1, v ) );
    return S.insert( node( pos, R, v ) ).first;
}

inline void Assign( Re int l, Re int r, Re bool v ){
    Re IT ed(Split(r + 1)), be(Split(l));
    S.erase( be, ed );
    S.insert(node( l, r, v ));
}

inline void Change( Re int l, Re int r ){
    Re IT ed(Split(r + 1)), be(Split(l));
    for ( Re IT it = be; it != ed; ++it ) it->val = !(it->val);
}

inline int Get1( Re int l, Re int r ){
    Re IT ed(Split(r + 1)), be(Split(l)); Re int ans(0);
    for ( Re IT it = be; it != ed; ++it ) if ( it->val ) ans += (it->r) - (it->l) + 1;
    return ans;
}

inline int Get2( Re int l, Re int r ){
    Re IT ed(Split(r + 1)), be(Split(l)); Re int ans(0), cur(0);
    for ( Re IT it = be; it != ed; ++it )
        if ( it->val ) cur += (it->r) - (it->l) + 1;
        else ans = max( ans, cur ), cur = 0;
    ans = max( ans, cur );
    return ans;
}

int N, M, t, ls;
int a[100005];

int main(){
    scanf( "%d%d", &N, &M ); ls = 1; 
    for ( Re int i = 1; i <= N; ++i ) scanf( "%d", &a[i] );
    for ( Re int i = 2; i <= N; ++i ) if ( a[i] ^ a[i - 1] ) S.insert( node( ls, i - 1, a[i - 1] ) ), ls = i;
    S.insert( node( ls, N, a[N] ) );
    for ( Re int i = 1; i <= M; ++i ){
        Re int op, a, b; scanf( "%d%d%d", &op, &a, &b ); a++; b++;
        if ( op < 2 ) Assign( a, b, op );
        if ( op == 2 ) Change( a, b );
        if ( op == 3 ) printf( "%d\n", Get1( a, b ) );
        if ( op == 4 ) printf( "%d\n", Get2( a, b ) );
    }
    return 0;
}