[Tricks-00006]CF1558E 如何处理无向图中的任意环?tourist 题,太神啦。

题意:

自己看去。不过有个限制别忘了:每个点的度数都至少为 \(\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;
}
posted @ 2024-12-08 20:08  maihe  阅读(52)  评论(1)    收藏  举报