旅行(加强版)「基环树」

题目描述

这不是个链接

思路分析

我也不知道我为什么突然脑抽花两个小时搞这个加强版

  • 原题暴力删边 \(O(n^2)\) 水过了,然而在这道题里显然会直接 \(T\)
  • 对于没有环的情况,我们是需要按子节点从小到大进行深搜的(即不存在未访问完子树就回溯),如果不一直搜到叶节点的话就没有机会访问到了。(这里要是不懂建议重新读题)。
  • 对于有环的情况,我们是可以进行回溯的但回溯机会只有一次,否则会存在有的环上的点没被访问。我们通过 \(Tarjan\) 找到环,并同时找到环上第一个访问的点 \(fst\),这个过程不再赘述。
    • 如果这只是个简单的环,即环上不存在其它支链,这种情况也很简单。在环上与 \(fst\) 相连的一定至少有两个点,我们在进入这个环时一定得优先选最小的那个在环上的点,如果遇到一个较大的点,这时可以回溯到与 \(fst\) 相连的最小的还没访问的点,可以设一个 \(fail\) 来标记。
    • 但如果环上还有很多支链像一个钥匙串一样呢?这时候就不能无脑回溯了,如果当前的环上的点与 \(fst\) 的路径之间有支链,那这些支链必须在回溯到 \(fst\) 之前被访问到,否则也会没有机会再被访问到。那么这些支链如果没有直接被与这些支链相连的环上的点访问,就必须在回溯到 \(fst\) 之前回溯到这条支链,所以需要更新上面所设的 \(fail\) 来保证这条支链在接下来不久也会被访问。
  • 这样回溯的条件其实也就出来了:对于当前位于环上的节点 \(x\) ,只有与 \(x\) 的相连的位于环上的点在所有与 \(x\) 相连的点最大时,即最后才访问到,才可以回溯。在环上是因为只有在环上才可以保障可以通过环的另一半重新访问到这个点而不被漏掉;最大是因为只有最大才能保证其他与 \(x\) 相连的不在环上的点在之前已经被遍历到,而也不会被漏掉。还有一种可能就是最大的点是父节点且在环上(即已经被访问),这时候就可以将上面的最大替换成次大。

小蒟蒻成功地写出了没人能看懂的题解

\(Code\)

#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<vector>
#define R register
#define N 500010
using namespace std;
inline int read(){
	int x = 0,f = 1;
	char ch = getchar();
	while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
	return x*f;
}
int n,m,sta[N],top,dfs_clock,fst,dfn[N],low[N],fail;
bool vis[N],circle,incir[N],flag;
vector<int>g[N];
void Tarjan(int u,int fa){//Tarjan 找环
	dfn[u] = low[u] = ++dfs_clock;
	sta[++top] = u;
	for(R int i = 0;i < g[u].size();i++){
		int v = g[u][i];
		if(v==fa)continue;
		if(dfn[v]){
			low[u] = min(low[u],dfn[v]);
			fst = v;//说明v是环上第一个被访问的点
			circle = 1;
		}else{
			Tarjan(v,u);
			low[u] = min(low[u],low[v]);
		}
		if(circle)return;//找到环就退出就行了
	}
	if(dfn[u]==low[u]){//小细节,这里不需要用循环,因为只有一个环
		top--;
	}
}
void get_circle(){
	while(top){
		int x = sta[top--];
		incir[x] = 1;
		if(x==fst)break;
	}
}
void dfs(int u){
	printf("%d ",u);
	vis[u] = 1;
	if((!incir[u])||flag){//flag标记是否已经回溯过,如果已经回溯过或者不在环上,直接dfs就好了
		for(R int i = 0;i < g[u].size();i++){
			int v = g[u][i];
			if(vis[v])continue;
			dfs(v);
		}
	}
	else if(incir[u]){
		if(u==fst){//找出最初的 fail
			for(R int i = 0;i < g[u].size();i++){
				int v = g[u][i];
				if(vis[v])continue;
				if(incir[v]){
					fail = g[u][i+1];
					break;
				}
			}
		}
		for(R int i = 0;i < g[u].size();i++){
			int v = g[u][i];
			if(vis[v])continue;
			if(incir[v]&&flag==0&&fail<v&&(i==g[u].size()-1||(i==g[u].size()-2&&vis[g[u][i+1]]))){
                                                   //满足上面的回溯条件,且会使答案更优
				flag = 1;
				dfs(fail);
			}
			else if(flag)dfs(v);//回溯过了直接dfs
			else if(incir[v]){//在环上但还有其它支链没被访问
				int j = i+1;
				while(vis[g[u][j]])j++;
				if(g[u][j])fail = g[u][j];
				dfs(v);
			}
			else dfs(v);
		}
	}
}
int main(){
	n = read(),m = read();
	for(R int i = 1;i <= m;i++){
		int x = read(),y = read();
		g[x].push_back(y),g[y].push_back(x);
	}
	for(R int i = 1;i <= n;i++)sort(g[i].begin(),g[i].end());
	Tarjan(1,0);
	get_circle();
	fail = 0x3f3f3f3f;
	dfs(1);
	return 0;
}

小蒟蒻成功地写出了没人能看懂的代码

posted @ 2020-10-27 16:32  HH_Halo  阅读(121)  评论(3编辑  收藏  举报