1. 程式人生 > 其它 >初見 | 資料結構 | ODT

初見 | 資料結構 | ODT

Chtholly Tree 簡記

「啟」

  • 關於為啥我要學這個?

    • 閒的。

本篇中所有 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 ?