「學習筆記」珂朵莉樹 ODT
珂朵莉樹,也叫ODT(Old Driver Tree 老司機樹)
從前有一天,珂朵莉出現了。。。
然後有一天,珂朵莉樹出現了。。。
看看圖片的地址 Codeforces可還行)
沒錯,珂朵莉樹來自Codeforces 896C C. Willem, Chtholly and Seniorious
國外珂學家 滑稽)
前置芝士:
set的基本操作
迭代器(跟指標差不多
過載運算子、建構函式的簡單瞭解
mutable(下面也會講
暴力列舉
常數優化(inline O2 O3 register大法好啊
夠簡單了吧?除了真正的小白,大家都應該有所瞭解。
廢話完了,扯進正題(畢竟你不是珂學家,你是個O·I·E·R
珂朵莉樹的適用範圍(缺一不可,不然複雜度就是不正確的,很容易被卡):
- 資料純隨機
- 有區間修改操作
大概就這兩個吧。珂朵莉樹畢竟是一種騙分演算法(珂朵莉:我不服),想到正解儘量用正解。
珂朵莉樹的主要思想就是用一個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,否則還要刪除元素,再插入進去,降低了效率。因為珂朵莉樹比較暴力,我們要儘可能優化複雜度。
- 建立你的珂朵莉樹
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
炒雞簡單對吧?
- 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]的迭代器
- 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指向的區間刪了。
- 區間取反
暴力列舉即可(也要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的個數
也很暴力,一個個列舉
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的個數
還是暴力
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;
}