傳送門
給定乙個無向連通圖,不能走已經經過的點,可以回溯,每到乙個新點記錄編號,求字典序最小的編號序列。
點數 \(n\) 和邊數 \(m\) 的關係:\(m \in \\)
\(1 \le n \le 5\times10^5\)
看完這題後沒啥思路……但一看 \(m \in \\),感覺到好像不太對。也就是這張圖只可能是乙個樹或者乙個基環樹?
那就分情況討論唄。
也就是樹。
直接從1號節點開始,從小到大遍歷出邊的頂點進行dfs即可。
然後,就沒有然後了……
這個竟然佔了 \(60 \texttt\),划算到**(
基環樹。
基環樹就是樹連了一條邊,也就是樹中帶乙個環。
首先考慮將環上的所有節點標記上:
bool flcyc;
void dfscyc(int u, int fa)
dfscyc(v, u);
if (cyc[v] && flcyc) }}
}
然後可以想到,基環樹中一定有一條邊沒有走過。(並且這條邊在環上)
這個其實很好理解,基環樹上的環的邊如果都走過,那就不可能滿足 每個點除了第一次訪問或者回溯不能再次訪問 這一題目條件了。那麼我們可以暴力刪邊跑 \(m = n - 1\),\(\mathcal(n^2)\)。這個能過弱化,但是本題資料顯然過不了,考慮在dfs上做手腳。
首先從 \(1\) 開始一直dfs,直到到達在環上的節點。
我們定義一次「反悔操作」為對於乙個節點,沒遍歷完所有子節點就回到上乙個節點的操作。
顯然,\(m = n - 1\) 時不需要,也不能進行反悔操作(否則會有點到不了),但是 \(m = n\) 可以反悔一次使得答案更優。(不能反悔兩次)
理論上講太晦澀,我們舉個例子。
這張圖如果按照正常dfs跑,答案是1 2 3 4 6 7 5
但是如果在3跑完4和6的時候,使用反悔**,退到2節點,然後繼續,答案就是1 2 3 4 6 5 7
,顯然後者更優。
那麼現在問題來了,反悔只能用一次,該用在什麼時候呢?
首先,如果你還有不在環上的孩子沒走完,你能反悔嗎?不能。如果你現在反悔,那麼那些不在環上的孩子城市就永遠無法到達。
換句話說,所有不在環上的孩子走完,只剩乙個在環上的孩子,你才可以選擇反悔。
那剩下的問題就簡單了。現在只需要考慮只有乙個沒走完的孩子在環上的點能否反悔。
首先來看看反悔的本質能給我們帶來什麼好處吧。
我們把沒反悔之前本來要走的節點記為 \(p\),上乙個還有孩子沒走完的祖先的下乙個要走的孩子(其實就是反悔到的位置)記為 \(q\)。
比如上邊那張圖,本來我要遍歷到 \(p = 7\) 了,結果反悔到了上乙個還沒有走完孩子的祖先 \(2\),它下乙個要走的孩子是 \(q = 5\).
那麼字典序本來這個要填\(p = 7\),現在因為反悔要填 \(q = 5\) 了。
字典序前面的遍歷序列已經確定,而字典序又是在前面的做主,那麼顯然決定字典序的只在於 \(p\) 和 \(q\) 的大小關係,哪個小對應的字典序就小。因此,如果 \(q < p\),就可以得到乙個更小的字典序,也就是說這次反悔划算。如果 \(q > p\),那就不划算了。
還有乙個問題:是早反悔好還是晚反悔好呢?
當然是早反悔好了!早反悔,可以把字典序越前面的數變小,那麼整個字典序顯然比晚反悔優。
總結一下,當同時滿足以下三個條件時:
那就立刻反悔。
其他情況正常dfs即可,那麼 \(\mathtt\) 就這麼華麗麗的結束了。
暴力刪邊: \(\mathcal(n^2)\),較緊。
正解:\(\mathcal(n\log n)\)。
帶乙個 \(\log\) 是因為dfs的時候要從小到達選擇出邊點。有兩種解決方法:
不管哪種,複雜度都會帶乙個 \(\log\)。
ps:據說可以用一種類sa的基數排序思想使得 \(\log\) 降掉。整體時間複雜度可以降為 \(\mathcal(n)\)。不過常數較大……
/*
* @author: crab-in-the-northeast
* @date: 2020-11-28 10:37:32
* @last modified by: crab-in-the-northeast
* @last modified time: 2020-11-29 17:25:54
*/#include inline int read()
while (ch >= '0' && ch <= '9')
if (f)
return x;
return ~(x - 1);
}const int maxn = 500005;
const int maxm = 500005;
const int maxinf = 0x3f3f3f3f;
struct edges e[maxm << 1];
int head[maxn], ecnt;
int ans[maxn], cnt;
bool vis[maxn], cyc[maxn];
inline void insert(int u, int v) ;
head[u] = ecnt;
return ;
}bool flcyc;
void dfscyc(int u, int fa)
dfscyc(v, u);
if (cyc[v] && flcyc) }}
}bool fl;
void dfs(int u, int fa, int back)
while (!q.empty())
if (!vis[v])
dfs(v, u, (!q.empty() && cyc[u]) ? q.top() : back);
}}int main()
dfscyc(1, 1);
std :: memset(vis, 0, sizeof(vis));
dfs(1, 1, maxinf);
for (int i = 1; i <= cnt; ++i)
std :: printf("%d ", ans[i]);
puts("");
return 0;
}
基環樹找環這種基本操作一定要會,然後考場上別想複雜,大膽暴力 \(n ^ 2\) 是可以過樸素資料的。
類sa的基數排序優化就不寫了。因為常數挺大的,寫了並沒有什麼用(
luogu p1081 開車旅行
傳送門 此題為複雜細節題,無法總結題意,所以給出原題 小 text 和小 text 決定利用假期外出旅行,他們將想去的城市從 1 到 n 編號,且編號較小的城市在編號較大的城市的西邊,已知各個城市的海拔高度互不相同,記城市 i 的海拔高度為 h i 城市 i 和城市 j 之間的距離 d 恰好是這兩個...
luogu p1081 開車旅行
傳送門此題為複雜細節題,無法總結題意,所以給出原題 小 text 和小 text 決定利用假期外出旅行,他們將想去的城市從 1 到 n 編號,且編號較小的城市在編號較大的城市的西邊,已知各個城市的海拔高度互不相同,記城市 i 的海拔高度為 h i 城市 i 和城市 j 之間的距離 d 恰好是這兩個城...
Luogu P1137 旅行計畫
可以發現,因為只能往東邊走,並且有入度為 0 的起點,因此這是乙個有向無環圖,可以進行拓撲排序,求出拓撲序列。那麼我們要拓撲序列怎麼做呢?由於拓撲序列中,前面的點總是後面的點的前驅,因此可以進行dp。而dp的狀態轉移方程也很明顯,這個城市只能由前面的城市轉移過來,因此有方程 dis v max di...