【UR #23】地鐵規划


簡記思維碎屑,做法可能除了 \(\Theta(m^2)\) 的都和官方題解不太一樣

Description

一句話題意:有一個長度為 \(m\) 的邊序列,你需要使用交互庫提供的可撤銷並查集接口,在線地對於每個 \(l\) 求出最大的 \(r\),使得區間 \([l,r]\) 內的邊不形成環。

可行的操作為

  • merge(x) 表示合並第 \(x\) 條邊的兩個端點所在的並查集,如果已經同集合,則交互過程不合法,判 \(0\)

  • undo() 表示撤銷上一次操作,如果操作序列為空時進行則交互過程不合法

  • check(x) 表示檢查編號為 \(x\) 的兩個邊連接的兩條點是否已經在相同連通塊中,是則返回 \(0\)。換句話說即第 \(x\) 條邊是不是可以合並

可以執行的 merge 操作量級為 \(\Theta(m\log m)\),可以執行的 check 操作次數上界為 \(8\times 10^6\)

\(n\le 10^5,m\le 2\times 10^5\)

Solution

步步為營


Subtask 1

復雜度 \(\Theta(m^2)\)

對於每個詢問的 \(l\)checkmerge 找到 \(r\),函數返回前撤銷所有操作

期望得分:\(10\)


Subtask 2

復雜度 \(\Theta(m\sqrt m)\)

觀察上面做法 merge 操作冗余是什么?

其實是每次要彈出所有的邊是不必要的,由於本題詢問順序是固定的,在彈出第 \(x(x>1)\) 條邊時前面的邊已經被彈出了

為了讓 \(x\) 后面的邊 \(y\) 彈出時盡量減少操作序列中 \(>y\) 的邊的移動,可以將 \(y\) 也彈出來並放到當前的操作序列頂端

嘗試確定 \(y\) 的范圍,由於這檔限制很松,可以將操作棧中 \(x\) 之上的元素和 \(x\) 之下的 \(\sqrt m\) 個元素取出並按標號排序后逆序放回棧頂,如果棧中元素不夠 \(\sqrt m\) 個就全部取出

考察一個元素的移動次數,被移動一定是在操作棧中靠上的元素被刪除或者靠下的元素被刪除所致。

靠上的元素刪除時最多有 \(\sqrt m\) 個能導致其移動,即標號在 \([x-\sqrt m,x-1]\),靠下的元素刪除一個就會有 \(\sqrt m\) 個元素保持和它有序了,只會重排 \(\sqrt m\)

至於標號和 \(x\) 絕對差小於根號的元素導致的移動算常數不再贅述

期望得分: \(40\)

Subtask 3

保證數據隨機,復雜度 \(\Theta(\texttt{能過})\)

誒我根號做法為啥過不了呀?

Subtask 4

復雜度 \(\Theta(m\log m)\)

接着根號做法編,嘗試多種減少冗余的方式中可以選擇這樣子做:

每次刪除一個元素時,計算其到棧頂的距離,並在棧中再取出等量元素進行合並,合並即排序后逆序加入棧中

聽起來很簡潔是不是!體量小如此的做法也確實有線性對數復雜度!

證明仍然考察每個邊被移動只能是其靠上/靠下的邊刪除所致,那么在其下面的邊被刪除時每次合並的元素個數會乘 \(2\) 所以不超過 \(\log\)

其上面的元素被合並時每次需要花費 \(x\) 個元素讓 \(x\) 個元素移動一次並將段長減少 \(1\),復雜度甚至分析不出來高於 \(\Theta(m)\)

(這個量級有點難以置信,所以可能有問題,還請指教)

upd 2021-12-26 :被 wlzhouzhuan 卡到 480W 次操作了/kk

期望得分: \(100\)

const int N=2e5+10;
int n,m;
void init(int n,int _m,int lim){m=_m;}
int lans,st[N],top;
int solve(int x){
    if(x>1){
        vector<int> now;
        while(top){
            undo();
            if(x-1!=st[top]) now.pb(st[top--]);
            else{--top; break;}
        }
        int siz=now.size();
        while(siz--&&top){
            undo();
            now.pb(st[top--]);
        }
        sort(now.begin(),now.end());
        while(now.size()){
            merge(st[++top]=now.back());
            now.pop_back();
        }
    }
    int ans=lans+1;
    while(ans<=m&&check(ans)) merge(ans),st[++top]=ans,++ans; 
    --ans;
    return lans=ans;
}

總結

其實個人想的做法是讓邊分組,數量到一定量級就重構,嘗試了一些重構方式但是都不能簡單證明復雜度合法,甚至一種做法和正解 一致 類似都是取出大小相同的一塊

賽時嘗試證明復雜度是方式通過合並邊所在塊的個數,賽后改成在操作棧中低於/高於之的就清晰了許多,而且也能意識到編的分塊重構沒有意義

題目還是總體有趣的,只是我自己賽時沒有想清楚,也沒有必要放馬后炮了


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM