• [洛谷] [NOIP2018 提高组] 旅行 加强版


    题目链接:https://www.luogu.com.cn/problem/P5049

    题目描述

    小 Y 是一个爱好旅行的 OIer。她来到 X 国,打算将各个城市都玩一遍。

    小Y了解到, X国的 n 个城市之间有 m 条双向道路。每条双向道路连接两个城市。 不存在两条连接同一对城市的道路,也不存在一条连接一个城市和它本身的道路。并且, 从任意一个城市出发,通过这些道路都可以到达任意一个其他城市。小 Y 只能通过这些 道路从一个城市前往另一个城市。

    小 Y 的旅行方案是这样的:任意选定一个城市作为起点,然后从起点开始,每次可 以选择一条与当前城市相连的道路,走向一个没有去过的城市,或者沿着第一次访问该 城市时经过的道路后退到上一个城市。当小 Y 回到起点时,她可以选择结束这次旅行或 继续旅行。需要注意的是,小 Y 要求在旅行方案中,每个城市都被访问到。

    为了让自己的旅行更有意义,小 Y 决定在每到达一个新的城市(包括起点)时,将 它的编号记录下来。她知道这样会形成一个长度为 nn 的序列。她希望这个序列的字典序 最小,你能帮帮她吗? 对于两个长度均为 nn 的序列 AA 和 BB,当且仅当存在一个正整数 xx,满足以下条件时, 我们说序列 AA 的字典序小于 BB。

    对于任意正整数 1 ≤ i < x1≤i<x,序列 A 的第 i 个元素Ai 和序列 B 的第 i 个元素 Bi相同。
    序列 A 的第 x 个元素的值小于序列 B 的第 x 个元素的值。

    输入格式

    输文件共 m+1 行。第一行包含两个整数 n,m(m≤n),中间用一个空格分隔。

    接下来 m 行,每行包含两个整数 u,v(1≤u,v≤n) ,表示编号为 u 和 v 的城市之 间有一条道路,两个整数之间用一个空格分隔。

    输出格式

    输出文件包含一行,nn 个整数,表示字典序最小的序列。相邻两个整数之间用一个 空格分隔。

    输入

    6 5 
    1 3 
    2 3 
    2 5 
    3 4 
    4 6
    

    输出

    1 3 2 5 4 6
    

    输入

    6 6 
    1 3 
    2 3 
    2 5 
    3 4 
    4 5 
    4 6
    

    输出:

    1 3 2 4 5 6
    

    该题m有两种情况,对于m = n - 1的情况,这是一颗树,我们只需要从1开始,往后遍历时,每次选择所连的点(未被访问过)中,编号最小的那个(贪心)
    对于m = n的情况下,图中绝对会有一个环,这种图叫做基环树,
    遇见基环树,首要的任务是找环
    ① 在学习Tarjan算法的时候,我们可以知道改环中任意两点都是可以相互到达的(SCC),所以说我们可以用Tarjan算法来找出环 最稳妥
    ② 可以用dfs来模拟Tarjan算法的过程来找环
    以下为本人两种常用的dfs找环的代码,均可通过此题

    bool vis[500007];
    int in[500007],tot,rt;
    int getLoop(int u,int fa) {
    	if(vis[u]) {
    		rt = u;
    		return 1;
    	}
    	int tmp;
    	vis[u] = true;
    	for(int i=head[u]; ~i; i=e[i].nex) {
    		int to = e[i].v;
    		if(to == fa) continue;
    		tmp = getLoop(to,u);
    		if(tmp) {
    			if(tmp == 1) {
    				in[u] = 1;
    				if(u != rt) return 1;
    			}
    			return 2;
    		}
    	}
    	return 0;
    }
    
    int getLoop2(int u,int fa) {
    	vis[u] = true;
    	for(int i=head[u]; ~i; i=e[i].nex) {
    		int to = e[i].v;
    		if(to == fa) continue;
    		if(vis[to]) {
    			in[u] = 1;
    			return to;
    		} else {
    			int ret = getLoop2(to,u);
    			if(ret == 0) return 0;
    			if(ret == -1) continue;
    			in[u] = 1;
    			if(ret == u) return 0;
    			else return ret;
    		}
    	}
    	return -1;
    }
    

    对于基环树的情况,大致有两种复杂度的情况{
    1. 找到环,然后枚举环上的边,断掉一条边之后,便到了树的情况
    2. 因为基环树比树多了一条边,所以在 dfs找答案的时候就可以选择一条边不走。按照贪心策略在dfs过程中,考虑在什么情况下会选择舍弃一条边?{
    ① 毋庸置疑,首先我们舍弃的这一条边是在环上的
    ② 舍弃了这条边之后,就立即进行回溯,因为舍弃的边连接的是当前点的最大儿子节点
    ③ 我们在回溯之后,再往后走的那个点的编号一定是比舍弃的那条边连接的点要小
    }
    }

    以上两种找环的方式时间复杂度相差不大(几乎没有差距)
    在这里插入图片描述

    int cnt,head[500007],n,m,flag;
    vector<int> ans;
    struct node {
    	int v,nex,u;
    } e[maxn];
    void add(int u,int v) {
    	e[cnt].u = u;
    	e[cnt].v = v;
    	e[cnt].nex = head[u];
    	head[u] = cnt ++;
    }
    void init() {
    	cnt = 0;
    	for(int i=0; i <= 500000; i++) head[i] = -1;
    }
    bool vis[500007];
    int in[500007],tot,rt;
    int getLoop(int u,int fa) {
    	if(vis[u]) {
    		rt = u;
    		return 1;
    	}
    	int tmp;
    	vis[u] = true;
    	for(int i=head[u]; ~i; i=e[i].nex) {
    		int to = e[i].v;
    		if(to == fa) continue;
    		tmp = getLoop(to,u);
    		if(tmp) {
    			if(tmp == 1) {
    				in[u] = 1;
    				if(u != rt) return 1;
    			}
    			return 2;
    		}
    	}
    	return 0;
    }
    int getLoop2(int u,int fa) {/// pre_version
    	vis[u] = true;
    	for(int i=head[u]; ~i; i=e[i].nex) {
    		int to = e[i].v;
    		if(to == fa) continue;
    		if(vis[to]) {
    			in[u] = 1;
    			return to;
    		} else {
    			int ret = getLoop2(to,u);
    			if(ret == 0) return 0;
    			if(ret == -1) continue;
    			in[u] = 1;
    			if(ret == u) return 0;
    			else return ret;
    		}
    	}
    	return -1;
    }
    void dfs(int u,int fa) {
    	ans.push_back(u);
    	vis[u] = true;
    	vector<int> vet;
    	for(int i=head[u]; ~i; i=e[i].nex) {
    		int to = e[i].v;
    		if(vis[to]) continue;
    		vet.push_back(to);
    	}
    	sort(vet.begin(),vet.end());
    	int siz = vet.size();
    	for(int i=0; i<siz; i++) {
    		int to = vet[i];
    		if(vis[to]) continue;
    		if(flag && i == siz - 1 && in[u] && in[to] && to > fa) {
    			flag = 0;
    			continue;
    		}
    		if(i + 1 < siz) dfs(to,vet[i+1]);
    		else dfs(to,fa);
    	}
    }
    int main() {
    	n = read,m = read;
    	init();
    	for(int i=1; i<=m; i++) {
    		int u = read,v = read;
    		add(u,v),add(v,u);
    	}
    	memset(vis,0,sizeof vis);
    	if(n == m) getLoop(1,0),flag = 1;/// ac 2 3 4 5
    	memset(vis,0,sizeof vis);
    	dfs(1,n+1);
    	for(int i=1; i<=n; i++) {
    		printf("%d%c",ans[i-1],i<n?' ':'
    ');
    	}
    	return 0;
    }
    /**
    6 6
    1 3
    2 3
    2 5
    3 4
    4 5
    4 6
    
    **/
    
    
    

    Tarjan做法:
    在这里插入图片描述
    时间相差不大,可能多了个 stl 的常数吧

    int cnt,head[500007],n,m,flag;
    vector<int> ans;
    struct node {
    	int v,nex,u;
    } e[maxn];
    void add(int u,int v) {
    	e[cnt].u = u;
    	e[cnt].v = v;
    	e[cnt].nex = head[u];
    	head[u] = cnt ++;
    }
    void init() {
    	cnt = 0;
    	for(int i=0; i <= 500000; i++) head[i] = -1;
    }
    bool vis[500007];
    int in[500007],tot,tim;
    int dfn[500007],low[500007];///,pos[500007];
    stack<int> st;///Tarjan use
    void Tarjan(int x,int fa) {
    	dfn[x] = low[x] = ++ tim;
    	st.push(x);
    	vis[x] = 1;
    	for(int i=head[x]; ~i; i=e[i].nex) {
    		int to = e[i].v;
    		if(to == fa) continue;
    		if(!dfn[to]) {
    			Tarjan(to,x);
    			low[x] = min(low[x],low[to]);
    		} else if(vis[to] == 1) {
    			low[x] = min(low[x],dfn[to]);
    		}
    	}
    	if(low[x] == dfn[x]) {
    		vector<int> path;
    		path.push_back(x);
    		while(st.top() != x) {
    			path.push_back(st.top());
    			vis[st.top()] = 0;
    			st.pop();
    		}
    		st.pop();
    		if(path.size() >= 2) {
    			for(int i=0; i<path.size(); i++) in[path[i]] = 1;
    			return ;
    		}
    	}
    }
    void dfs(int u,int fa) {
    	ans.push_back(u);
    	vis[u] = true;
    	vector<int> vet;
    	for(int i=head[u]; ~i; i=e[i].nex) {
    		int to = e[i].v;
    		if(vis[to]) continue;
    		vet.push_back(to);
    	}
    	sort(vet.begin(),vet.end());
    	int siz = vet.size();
    	for(int i=0; i<siz; i++) {
    		int to = vet[i];
    		if(vis[to]) continue;
    		if(flag && i == siz - 1 && in[u] && in[to] && to > fa) {
    			flag = 0;
    			continue;
    		}
    		if(i + 1 < siz) dfs(to,vet[i+1]);
    		else dfs(to,fa);
    	}
    }
    int main() {
    	n = read,m = read;
    	init();
    	for(int i=1; i<=m; i++) {
    		int u = read,v = read;
    		add(u,v),add(v,u);
    	}
    	memset(vis,0,sizeof vis);
    	if(n == m) Tarjan(1,0),flag = 1;/// ac 2 3 4 5
    	memset(vis,0,sizeof vis);
    	dfs(1,n+1);
    	for(int i=1; i<=n; i++) {
    		printf("%d%c",ans[i-1],i<n?' ':'
    ');
    	}
    	return 0;
    }
    /**
    6 6
    1 3
    2 3
    2 5
    3 4
    4 5
    4 6
    
    **/
    

    本题在找环的过程中,像极了2017年蓝桥杯决赛的一道题 发现环
    其实这个题再想一下的话,就可以发现,可以用拓扑排序做出来
    方法大同小异咯
    干脆也写了一下,拓扑排序写法:
    在这里插入图片描述

    #define Clear(x,val) memset(x,val,sizeof x)
    int cnt,head[500007],n,m,flag;
    vector<int> ans;
    struct node {
    	int v,nex,u;
    } e[maxn];
    void add(int u,int v) {
    	e[cnt].u = u;
    	e[cnt].v = v;
    	e[cnt].nex = head[u];
    	head[u] = cnt ++;
    }
    void init() {
    	cnt = 0;
    	for(int i=0; i <= 500000; i++) head[i] = -1;
    }
    bool vis[500007];
    int in[500007],tot,tim;
    int dfn[500007],low[500007];///,pos[500007];
    stack<int> st;///Tarjan use
    int deg[500007];
    void topoSort() {
    	queue<int> que;
    	for(int i=1; i<=n; i++) {
    		if(deg[i] == 1) que.push(i),vis[i] = 1;
    	}
    	while(que.size()) {
    		int frt = que.front();
    		que.pop(); 
    		if(deg[frt] == 1) in[frt] = 0;
    		for(int i=head[frt]; ~i; i=e[i].nex) {
    			int to = e[i].v;
    			deg[to] --;
    			if(deg[to] == 1) que.push(to);
    		}
    	}
    }
    void dfs(int u,int fa) {
    	ans.push_back(u);
    	vis[u] = true;
    	vector<int> vet;
    	for(int i=head[u]; ~i; i=e[i].nex) {
    		int to = e[i].v;
    		if(vis[to]) continue;
    		vet.push_back(to);
    	}
    	sort(vet.begin(),vet.end());
    	int siz = vet.size();
    	for(int i=0; i<siz; i++) {
    		int to = vet[i];
    		if(vis[to]) continue;
    		if(flag && i == siz - 1 && in[u] && in[to] && to > fa) {
    			flag = 0;
    			continue;
    		}
    		if(i + 1 < siz) dfs(to,vet[i+1]);
    		else dfs(to,fa);
    	}
    }
    int main() {
    	n = read,m = read;
    	init();
    	for(int i=1; i<=m; i++) {
    		int u = read,v = read;
    		add(u,v),add(v,u);
    		deg[u] ++;
    		deg[v] ++;
    	}
    	memset(vis,0,sizeof vis);
    	if(n == m) Clear(in,1),topoSort(),flag = 1;
    	memset(vis,0,sizeof vis);
    	dfs(1,n+1);
    	for(int i=1; i<=n; i++) {
    		printf("%d%c",ans[i-1],i<n?' ':'
    ');
    	}
    	return 0;
    }
    

    参考博客:https://blog.csdn.net/xyyxyyx/article/details/103010642

  • 相关阅读:
    Aizu 0033
    Aizu 0118
    【思维】贪心+细节——cf1361B
    【思维】构造+凸包+向量叉积——LEETCODE 游乐园的迷宫
    【思维】三元环计数+鸽笼定理/贪心——LEETCODE 游乐园的游览计划 好题
    dp+线性筛——LEETCODE切分数组
    【经典】带障碍的铺砖块——LEETCODE 覆盖
    【思维】树形dp+构造——leetcode二叉树任务调度
    【思维】状压dp—— 2020 联想杯 M
    【思维】建图+排列组合+预处理+最短路—— 2020 联想杯 E
  • 原文地址:https://www.cnblogs.com/PushyTao/p/15459807.html
Copyright © 2020-2023  润新知