初見 | 資料結構 | ODT
「啟」
-
關於為啥我要學這個?
- 閒的。
本篇中所有 Code 的預設源使用 「V5.2」.
「關於 ODT」
-
ODT 用處?
- (大多數是)在有區間賦值操作的 DS 題裡面騙分,因為好像專門為 ODT 設計的題不多吧?反正我只知道 CF896C.
-
時間複雜度?
-
ODT 的複雜度正確建立在資料隨機上,這點一定牢記。
-
對於所有的基礎操作(如 \(Assign\) 和 \(Add\) 等),使用
set
實現的 ODT 的複雜度為 \(O(n \log\log n)\),而連結串列實現的複雜度為 \(O(n \log n)\),不過我目前只會用set
實現就是了(
-
-
注意事項?
-
ODT 的複雜度正確建立在資料隨機上,ODT 的複雜度正確建立在資料隨機上,ODT 的複雜度正確建立在資料隨機上。不然的話出題人很容易構造資料讓你 T 掉。
-
別被沒有區間賦值的部分分卡了。
-
「實現」
先是核心思想:把值相同的區間合併成結點,存到 set
裡面。
於是就有了以下的結構體來存結點:
「結點 Node」
struct Node { LL l,r; mutable LL v; Node(LL l,LL r=0,LL v=0) : l(l),r(r),v(v) {} I bool operator < (const Node &co) const { Heriko l<co.l; } }; set<Node> s;
這裡的
mutable
是為了突破const
的限制,便於我們後面直接修改set
中的值,而不是拿出來改完再扔進去。
「分裂 Split」
\(Split\) 算是 ODT 中最重要的操作了,簡單來說就是把區間 \([l,r]\) 分成 \([l,pos-1]\) 和 \([pos,r]\) 兩段,便於我們操作。
實現也很簡單,我們先用 set
自帶的 lower_bound
確定 \(pos\) 對應位置,然後刪除原區間分成兩半插入。
I auto Split(LL pos) { auto it(s.lower_bound(Node(pos))); if(it!=s.end() and it->l==pos) Heriko it; --it; if(it->r<pos) Heriko s.end(); LL l(it->l),r(it->r),v(it->v); s.erase(it); s.insert(Node(l,pos-1,v)); Heriko s.insert(Node(pos,r,v)).first; }
這樣的話所有的區間 \([l,r]\) 上的操作都可以轉化為 \([Split(l),Split(r+1)].\)
「推平 Assign」
\(Assign\) 也是很重要操作,主要就是完成縮點的任務,實現起來也很簡單,找到區間之後刪除插入新的就行。
I void Assign(LL l,LL r,LL x)
{
auto itr(Split(r+1)),itl(Split(l));
s.erase(itl,itr);
s.insert(Node(l,r,x));
}
實際上最基本的操作也就上面這倆了,下面再擴充套件一點常用的操作。
「區間加 Add」
如何區間加吶?暴力。
嗯,沒錯就是暴力,找到對應區間之後暴力加就是了(
I void Add(LL l,LL r,LL x)
{
auto itr(Split(r+1)),itl(Split(l));
for(auto it(itl);it!=itr;++it)
it->v+=x;
}
「排名 Rank」
查詢區間排名的話,我們先宣告一個結構體或者 pair
便於對相同的數操作。
struct Rank
{
LL val,cnt;
Rank(LL val,LL cnt) : val(val),cnt(cnt) {}
I bool operator < (const Rank &co) const
{
Heriko val<co.val;
}
};
然後我們就用最好想的思路,先找到對應區間,然後把所有的數排序,直接去找要求排名即可。
I LL QueryRank(LL l,LL r,LL x)
{
auto itr(Split(r+1)),itl(Split(l));
vector<Rank> v;
for(auto it(itl);it!=itr;++it)
v.push_back(Rank(it->v,it->r-it->l+1));
sort(v.begin(),v.end());
LL i(0);
for(;i<(LL)v.size();++i)
if(v[i].cnt<x)
x-=v[i].cnt;
else
Heriko v[i].val;
Heriko v[i].val;
}
「其它 Other」
其實觀察上面的也能發現在 ODT 上的操作,先找到對應區間之後就很好辦了,所有大概的程式碼框架都是這個樣子:
I auto Function(int l,int r,...)
{
auto itr(Split(r+1)),itl(Split(l));
...
}
然後知道了這些就可以去把 CF896C 幹掉了。
「CF896C Code」
CI MXX(1e5+1),MOD(1e9+7);
LL n,m,seed,vmax,a[MXX];
I LL GetData()
{
LL res(seed);
seed=(seed*7+13)%MOD;
Heriko res;
}
I LL FstPow(LL x,LL y,LL p)
{
LL res(1);
x%=p;
while(y)
{
if(y&1)
(res*=x)%=p;
(x*=x)%=p;
y>>=1;
}
Heriko res;
}
struct Node
{
LL l,r;
mutable LL v;
Node(LL l,LL r=0,LL v=0) : l(l),r(r),v(v) {}
I bool operator < (const Node &co) const
{
Heriko l<co.l;
}
};
set<Node> s;
I auto Split(LL pos)
{
auto it(s.lower_bound(Node(pos)));
if(it!=s.end() and it->l==pos)
Heriko it;
--it;
if(it->r<pos)
Heriko s.end();
LL l(it->l),r(it->r),v(it->v);
s.erase(it);
s.insert(Node(l,pos-1,v));
Heriko s.insert(Node(pos,r,v)).first;
}
I void Assign(LL l,LL r,LL x)
{
auto itr(Split(r+1)),itl(Split(l));
s.erase(itl,itr);
s.insert(Node(l,r,x));
}
I void Add(LL l,LL r,LL x)
{
auto itr(Split(r+1)),itl(Split(l));
for(auto it(itl);it!=itr;++it)
it->v+=x;
}
struct Rank
{
LL val,cnt;
Rank(LL val,LL cnt) : val(val),cnt(cnt) {}
I bool operator < (const Rank &co) const
{
Heriko val<co.val;
}
};
I LL QueryRank(LL l,LL r,LL x)
{
auto itr(Split(r+1)),itl(Split(l));
vector<Rank> v;
for(auto it(itl);it!=itr;++it)
v.push_back(Rank(it->v,it->r-it->l+1));
sort(v.begin(),v.end());
LL i(0);
for(;i<(LL)v.size();++i)
if(v[i].cnt<x)
x-=v[i].cnt;
else
Heriko v[i].val;
Heriko v[i].val;
}
I LL QueryVal(LL l,LL r,LL x,LL y)
{
auto itr(Split(r+1)),itl(Split(l));
LL res(0);
for(auto it(itl);it!=itr;++it)
res=(res+FstPow(it->v,x,y)*(it->r-it->l+1)%y)%y;
Heriko res;
}
S main()
{
Files();
fr(n),fr(m),fr(seed),fr(vmax);
for(int i(1);i<=n;++i)
a[i]=(GetData()%vmax)+1,s.insert(Node(i,i,a[i]));
while(m--)
{
LL opt((GetData()%4)+1),l((GetData()%n)+1),r((GetData()%n)+1),x,y;
if(l>r)
swap(l,r);
if(opt==3)
x=(GetData()%(r-l+1))+1;
else
x=(GetData()%vmax)+1;
if(opt==4)
y=(GetData()%vmax)+1;
if(opt==1)
Add(l,r,x);
else if(opt==2)
Assign(l,r,x);
else if(opt==3)
fw(QueryRank(l,r,x),1);
else
fw(QueryVal(l,r,x,y),1);
}
Heriko Deltana;
}
「其它例題」
調了三天 CF896C 最後發現是快速冪少了 x%=p
之後就做了一點簡單 ODT 板子題。
「HAOI2014 貼海報」
這個題巨大顯然了吧,賊板子吧。
只需要區間推平,最後開個桶記錄一下就行了,直接切了對吧。
CI MXX(1001);
struct Node
{
int l,r;
mutable int val;
Node(int l,int r=0,int val=0) : l(l),r(r),val(val) {}
I bool operator < (const Node &co) const
{
Heriko l<co.l;
}
};
set<Node> s;
I auto Split(int pos)
{
auto it(s.lower_bound(Node(pos)));
if(it!=s.end() and it->l==pos)
Heriko it;
--it;
if(it->r<pos)
Heriko s.end();
int l(it->l),r(it->r),v(it->val);
s.erase(it);
s.insert(Node(l,pos-1,v));
Heriko s.insert(Node(pos,r,v)).first;
}
I void Assign(int l,int r,int v)
{
auto itr(Split(r+1)),itl(Split(l));
s.erase(itl,itr);
s.insert(Node(l,r,v));
}
int n,m,x,y,tot,ans(-1);
bitset<MXX> vis;
S main()
{
Files();
fr(n),fr(m);
s.insert(Node(1,n+1));
while(m--)
fr(x),fr(y),Assign(x,y,++tot);
for(auto it(s.begin());it!=s.end();++it)
if(!vis[it->val])
++ans,vis[it->val]=1;
fw(ans,1);
Heriko Deltana;
}
「CF343D Water Tree」
這個題是個樹上問題,比較板的樹剖(
不過我們不寫線段樹,我們直接上 ODT,在兩邊 DFS 處理出來 id 序之後按照普通的序列操作即可。
第二個操作就需要我們在 DFS 的時候記錄一下 top
,修改的時候不斷跳 top
進行 \(Assign\) 即可。
CI MXX(5e5+5);
struct ODT
{
int l,r;
mutable int v;
ODT(int l,int r=0,int v=0) : l(l),r(r),v(v) {}
I bool operator < (const ODT &co) const
{
Heriko l<co.l;
}
};
set<ODT> s;
I auto Split(int pos)
{
auto it(s.lower_bound(ODT(pos)));
if(it!=s.end() and it->l==pos)
Heriko it;
--it;
if(it->r<pos)
Heriko s.end();
int l(it->l),r(it->r),val(it->v);
s.erase(it);
s.insert(ODT(l,pos-1,val));
Heriko s.insert(ODT(pos,r,val)).first;
}
I void Assign(int l,int r,int x)
{
auto itr(Split(r+1)),itl(Split(l));
s.erase(itl,itr);
s.insert(ODT(l,r,x));
}
struct Node
{
int nex,to;
}
r[MXX<<1];
int rcnt,head[MXX];
I void Add(int x,int y)
{
r[++rcnt]=(Node){head[x],y},head[x]=rcnt;
r[++rcnt]=(Node){head[y],x},head[y]=rcnt;
}
int n,m,sz[MXX],dep[MXX],id[MXX],top[MXX],fa[MXX],son[MXX],tot;
void DFS1(int x,int fath)
{
sz[x]=1,fa[x]=fath,dep[x]=dep[fath]+1;
for(int i(head[x]);i;i=r[i].nex)
{
int y(r[i].to);
if(y==fath)
continue;
DFS1(y,x);
sz[x]+=sz[y];
if(sz[y]>sz[son[x]])
son[x]=y;
}
}
void DFS2(int x,int tp)
{
top[x]=tp,id[x]=++tot;
if(son[x])
DFS2(son[x],tp);
for(int i(head[x]);i;i=r[i].nex)
{
int y(r[i].to);
if(y==fa[x] or y==son[x])
continue;
DFS2(y,y);
}
}
I void ModifyZero(int x)
{
int tp(top[x]);
while(tp!=1)
{
Assign(id[tp],id[x],0);
x=fa[tp],tp=top[x];
}
Assign(id[1],id[x],0);
}
S main()
{
Files();
fr(n);
for(int i(1);i<n;++i)
{
int x,y;
fr(x),fr(y);
Add(x,y);
}
DFS1(1,0);
DFS2(1,1);
s.insert(ODT(0,MXX));
fr(m);
while(m--)
{
int opt,x;
fr(opt),fr(x);
if(opt==1)
Assign(id[x],id[x]+sz[x]-1,1);
else if(opt==2)
ModifyZero(x);
else
fw(Split(id[x])->v,1);
}
Heriko Deltana;
}
「終」
那麼就寫這些吧。
Do you like WHAT YOU SEE ?