[Noip2018]旅行(資料加強版)
[Noip2018]旅行(資料加強版)
一.前言
之前有寫過原版題目(\(O(n^2)\))的題解,掛一個連結,然後這次肝了一下加強版,加強版題目連結。沒有判是不是fa調了一下午qwq……
二.思路
我是標準的部分分寫法,只是一個樹的話很好做,直接每次貪最小就好,這裡不加贅述。
vector<int> q[MAXN]; void dfs60(int x,int fa){ printf("%d ",x); sort(q[x].begin(),q[x].end()); for(int i=0;i<q[x].size();++i){ if(q[x][i]==fa)continue; dfs60(q[x][i],x); } }
然後是 \(m=n\) 的基環樹。基環樹,顧名思義,是基於一個環的樹,簡單來講就是將樹上兩個點連起來,會形成一個環,斷掉這個環上任意一邊還是樹……
定義都不太重要,從中也許能得到n方做法,但是是過不了加強版的(n方移步原版),在加強版中,我選擇直接貪心過。在本做法中,關於基環樹,只需要知道一個點:我可以反悔一次。具體來講,就是不走接下來的在環上的另一個點 v,而這個反悔僅限於環上的點 u。
進一步解釋,之前是樹的時候,我沒得選,
必須乖乖的把自己的子樹走完。而現在有了環,我可以有一次選擇不繼續走下去,而是往回走,從環的另一個方向走到本該走的點(下面用圖畫舉例)
也就是從紅點到藍點我有兩條路可以走(看箭頭),如果我此時反悔不走紅線,我還有走藍色這條線的機會。
明確了這僅有一次的操作後,再來看另一個。
咱現在在紅點做出了不走藍點反悔走父親灰點的決策,那麼在這個決策中,父親灰點有已經走過的黑點兒子和沒有走過的黃點兒子,紅點也有沒有走過的黃點兒子。
那麼在反悔回溯的過程中,我們必須由下至上將所有的黃兒子都走了才行,不然完不成走完所有的要求。
有了上面的鋪墊,這道題就可以爆切了。只需要維護一個反悔後第一個要走的黃兒子就行。判斷它和接下來要走的藍點哪個大,如果藍點更大就反手一個回溯解決問題。關於黃兒子的維護:如果當前點(紅點)按照從小到大依次遍歷兒子,到了下一個點是在環上的藍點,且還有兒子沒有走(黃點),那麼更新維護的黃兒子。特別的,如果沒有兒子可以走了,從父親哪裡繼承。
最後涉及到一個找環的問題(可以亂寫),我採用深搜找環,壓棧的方式,這裡看每個人有不同。
三.CODE
int stack[MAXN],top;
bool vis[MAXN],ish[MAXN],fl;
void findh(int x,int fa){
if(fl)return ;
vis[x]=1;stack[++top]=x;//壓棧
for(int i=0;i<q[x].size();++i){
if(q[x][i]==fa)continue;
if(vis[q[x][i]]){//有已經遍歷到的了,出現環
ish[q[x][i]]=1;
while(top&&stack[top]!=q[x][i]){
ish[stack[top--]]=1;//記錄環
}
fl=1;
return ;
}
findh(q[x][i],x);
if(fl)return ;
}
top--;//彈棧
}
int mx=MAXN,alf;//mx是維護的黃兒子
void dfs100(int x,int fa){
vis[x]=1;
printf("%d ",x);
sort(q[x].begin(),q[x].end());//排序以便從小到大
for(int i=0;i<q[x].size();++i){
if(q[x][i]==fa||vis[q[x][i]])continue;//父親或者一次性不反悔將環走完了
if(!ish[q[x][i]])dfs100(q[x][i],x);//下一個點不在環上維護就是了
else {
if(!alf){//有沒有反悔過的標記
int t=((i==q[x].size()-1)?mx:q[x][i+1]);
//取沒有遍歷過的下一個,沒有就繼承父親
if(t==fa)mx=((i==q[x].size()-2)?mx:q[x][i+2]);
//這裡特別注意(我調了很久才找到),判斷是否是父親
else mx=t;
}
if(mx<q[x][i]&&!alf){
alf=1;continue;//反悔
}
else dfs100(q[x][i],x);//走就完了
}
}
}
int main(){
n=read();m=read();
for(int i=1,x,y;i<=m;++i){
x=read();y=read();
q[y].push_back(x);
q[x].push_back(y);
}
if(m==n-1)dfs60(1,0);
else{
findh(1,0);//找環
memset(vis,0,sizeof(vis));
dfs100(1,0);//開始旅行
}
return 0;
}