[Tricks-00006]CF1558E 如何處理無向圖中的任意環?tourist 題,太神啦。

maihe發表於2024-12-08

題意:

自己看去。不過有個限制別忘了:每個點的度數都至少為 \(\geq 2\)

我寫這些 Trick 題解還是要說清思考方法。不過這個題確實有點難以觀察到了/ll

還是從簡單到難地去講吧:

第一件事。如果沒有後面那個不能返回的條件的限制。那麼其實可能有很多種想法,不過大體思路都是統一的:每次加一個點,然後馬上回到根,再加一個點,再回來。都只在已經擴充套件到的集合上加上一個點,或者說類似 Prim 的東西。

好了,說了一堆廢話,回到正題。

這個題一定也是一步一步擴充套件下去的,不難發現。可以擴充套件出什麼樣子的呢?注意到並不一定必須要是邊雙才行,有這樣的路徑:

一個天真的想法是,每次加一個這東西,下次再加,每次都加這樣形狀物。不過,我在思考本題的時候似乎遇到了一個問題:

如果直接走了藍鏈回根,如果下一步剛好(且只能)要走剛返回的這條邊出去,咋辦啊?似乎就多了個限制,不能直接加了,難過。

不過仔細想完之後,其實得到這樣一個調整:

每次走一條邊會堆在一個棧頂上,如果要回去,直接 pop,就好啦!

這是你重新回去想想,只要遇到相同,都這麼做不香嗎?其實你發現,只要彈出的是已訪問的點,就不會有錯!!!

因此我們就可以理清楚思路了!每次加一個不返回的路徑,只要碰到一個訪問過的點,就結束了,之後怎麼在已訪問的窩裡"橫"都行,大家都一家親。

然後加的路徑也要對應地修改下了。其實也未必是個環,就走到一個已經訪問到的點就 ok 了!

很好,目前有了個很不錯的思路。畫出圖長這樣:

其實,用心感受,加的是一個"耳朵"(不過和耳分解裡的耳是有區別的,這裡又不是邊雙),起始點和終止點都是已經訪問過的,中間不經過同一節點(經過了就應該在這裡直接加了),然後把這段路徑的所有點的訪問標記標 \(1\)

先二分初始的錢數,每次能加一個就加一個,這樣至少已經有了個看著就是 poly 複雜度的做法。但是怎麼進一步最佳化呢?

看眼瓶頸在哪裡。二分一個 \(\log\),至多會新增 \(n\) 輪,每輪尋找的複雜度會有小問題。一種想法是 dijkstra,跑一個最短路。這個有人實現了,複雜度沒問題,不過程式碼難度會大一些。

還有這樣一個做法:還是去想那個找"環"的過程。剛才的思路類似 bfs 的東西,這次我們沿 dfs 的思路去考慮:

從一個已訪問點開始走,進行依次遍歷,只要能走就去走。直到能回到一個已訪問的點為止。

這是個沒錯的思路,不過複雜度有點假。那麼我們必須保證:每個未訪問的點至多被遍歷到一次

要滿足這個事情,就必須考慮到一個點被遍歷到兩次的後果。找到最開始出現這種情況的點,因為沒重邊,所以開始時每個點都至多遍歷第一次。當重複遇到一個點時,有下列兩種情況:

其中第一種直接轉就可以了。第二種你仔細想想,因為是第一次遇到一個遍歷兩次的點,所以上一步肯定不同,把邊換方向。選 \(\sum b\) 較大的那條路先走,然後再走回去,也一定行!因此,第二次遇到一個點時,一定是直接可以得到答案的。

這樣,直接 dfs 遇到重複點或已訪問點的時候找出路徑就 ok 了。時間複雜度 \(O(nm\log a)\)

程式碼不算很難寫:

#include<bits/stdc++.h>
using namespace std;
void gai(int x,int p,long long d);
void dfs(int x);
int n,m,a[1005],b[1005];
vector<int>g[1005];
int vist[1005],pre[1005];
long long H,dist[1005];
bool fl;
void gai(int x,int p,long long d){
	if(!pre[x]){
		pre[x]=p;dist[x]=d;
		dfs(x);
	}else{
		while(!vist[x]){
			vist[x]=1;H+=b[x];
			x=pre[x];
		}
		while(!vist[p]){
			vist[p]=1;H+=b[p];
			p=pre[p];
		}
		fl=1;
	}
}
void dfs(int x){
	for(auto y:g[x])if(y!=pre[x]){
		if(fl)continue;
		if(vist[y]){
			while(!vist[x]){
				vist[x]=1;H+=b[x];
				x=pre[x];
			}
			fl=1;return;
		}
		if(H+dist[x]<=a[y])continue;
		gai(y,x,dist[x]+b[y]);
	}
}
bool ok(int x){
	H=x;
	for(int i=1;i<=n;++i)vist[i]=0;
	vist[1]=1;
	while(1){
		for(int i=1;i<=n;++i)pre[i]=0;
		fl=0;
		for(int i=1;i<=n&&!fl;++i)if(vist[i]){
			for(auto j:g[i])if(!vist[j]){
				if(H<=a[j])continue;
				if(!fl)gai(j,i,b[j]);
			}
		}
		if(!fl)break;
	}
	for(int i=1;i<=n;++i)if(!vist[i])return 0;
	return 1;
}
int main(){
	int T;
	scanf("%d",&T);
	while(T--){
		scanf("%d%d",&n,&m);
		int mx=0;
		for(int i=2;i<=n;++i)scanf("%d",&a[i]),mx=max(mx,a[i]);
		for(int i=2;i<=n;++i)scanf("%d",&b[i]);
		for(int i=1;i<=n;++i){
			g[i].clear();
		}
		for(int i=1;i<=m;++i){
			int u,v;
			scanf("%d%d",&u,&v);
			g[u].emplace_back(v);
			g[v].emplace_back(u);
		}
		int L=0,R=mx;
		while(L<=R){
			int mid=(L+R)>>1;
			if(!ok(mid))L=mid+1;
			else R=mid-1;
		}
		printf("%d\n",L);
	}
	return 0;
}

相關文章