Noip 2018 Day2 T1 P5022 旅行(基环树)

在这里插入图片描述

思路

根据题目所给的数据范围,考虑可能是 O ( n 2 ) O(n^2) O(n2)的复杂度的算法,

  1. n = m + 1 n=m+1 n=m+1的时候,是一棵树,很明显就是选择子节点中的小的那个点先遍历。
  2. n = m n=m n=m的时候,基环树,我们可以发现如果要遍历图中的所有顶点,那么必然有一条边是不会遍历到的,而这条边的选择,就会影响到结果序列中字典序的大小,也就是说你选择一条边不走,一旦到达这条边就跳过,那么在剩下的树中遍历的时候,结果是不同的,我们就暴力的去枚举这条边就好了。

注意建图的时候借助到了邻接矩阵,根据邻接矩阵在用前向星建图,使得每个节点的子节点都是按照从小到大的顺序排列的。

#include <iostream>
#include <cstring>
using namespace std;
const int maxn = 5050;
int n, m;
int grap[maxn][maxn], res[maxn];
struct edge {
	int from, to,nxt;
} edges[maxn<<1];
int tot, head[maxn];
void add_edge(int u, int v) {
	edges[tot] = edge{u, v, head[u]};
	head[u] = tot++;
}

int vis[maxn], ans[maxn], cnt1;
void dfs1(int now) {
	vis[now] = 1;
	ans[cnt1++] = now;
	for (int i = head[now]; ~i; i = edges[i].nxt) {
		int to = edges[i].to;
		if (vis[to]) continue;
		dfs1(to);
	}
}

int uu[maxn], vv[maxn], cnt2, flag, entrance;
// uu[i]-vv[i]环种第i条边的两个端点。
void get_loop(int now, int fa) {
	vis[now] = 1;
	for (int i = head[now]; ~i; i = edges[i].nxt) {
		int to = edges[i].to;
		if (to == fa) continue;
		if (!flag and vis[to]) { // 发现环的起点
			flag=1;
			entrance = to; // 环的入口
    		uu[cnt2]=now;  vv[cnt2]=fa; cnt2++;
			uu[cnt2]=to;  vv[cnt2]=now; cnt2++;
			return ;
		}
		get_loop(to, now);
   		if (flag) { //说明此点now在环上。
    		if (now != entrance) { //到达entrance的时候就是出环的时候
        		uu[cnt2]=now; vv[cnt2]=fa; cnt2++;
    		}
      	 	else flag = 0;
      		return ; //找到了环就不需要再找了
    	}
	}	
}

int tmp[maxn];
void dfs2(int now) {
	vis[now] = 1;
	tmp[cnt1++] = now;
	for (int i = head[now]; ~i; i = edges[i].nxt) {
		int to = edges[i].to;
		if (vis[to] or !grap[now][to]) continue;
		dfs2(to);
	}
}

bool check() {
	for (int i = 0; i < n; i++) {
    if (ans[i] < tmp[i]) return 0;
    else if (ans[i] > tmp[i]) return 1;   
  }
  return 0;
}

void update() {
	for (int i = 0; i < n; i++)
		ans[i] = tmp[i];
}

int main(int argc, char const *argv[])
{
	ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
	cin >> n >> m;
	memset(head, -1, sizeof head);
	for (int i=1; i<=m; i++) {
		int u, v;
     	cin >> u >> v;
		grap[u][v] = grap[v][u] = 1;
	}

	for (int u = 1; u <= n; u++) {
		for (int v = n; v >= 1; v--) {
			if (grap[u][v]) add_edge(u, v); // 按照从小到大的顺序建立邻接表(用的是前向星)
		}
	}
  
	if (m == n-1) dfs1(1);
	else {
		get_loop(1, 0);
		memset(ans, 0x3f, sizeof ans);
		int cir_num = cnt2; // 环中边的数量
      	for (int i = 0; i < cir_num; i++) {
			memset(vis, 0, sizeof vis);
			int delu = uu[i], delv = vv[i]; // 枚举要删除的两个环中的点
			grap[delu][delv] = grap[delv][delu] = 0;
			vis[1] = 1; cnt1 = 0;
			dfs2(1);
			grap[delu][delv] = grap[delv][delu] = 1;
			if (check()) update(); // 找到字典序最小的答案
		}
	}
	for (int i = 0; i < n; i++)
		cout << ans[i] << " ";
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值