1. 程式人生 > 實用技巧 >[Noip2018]旅行(資料加強版)

[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;
}