第二届绿色计算机大赛代码挑战赛(第二阶段C++图论题)

城市道路

挑战任务

  A 国的国土由若干个城市组成,这些城市分布在若干个岛屿上,城市之间由一些 双向 道路连接,并且 互相联通 。如果一条道路跨越了两个岛屿,那么这条道路就是一座桥。
  现在你有一张 A 国的地图,地图上标明了 A 国所有的城市和道路,但并未标明岛屿。
  其中 A 国的桥满足了以下条件:如果将一条道路 删去之后,存在两个城市 不再联通,那么这条道路 一定是 一座桥。
现在,请你回答以下两个问题:

  1. A 国一共有多少座桥;
  2. 从任一城市到另外任一城市 最多 需要经过多少座桥。

题意

  给出一张连通的无向图(有重边),求出所有的桥,和任意两点间桥的数量的最大值。
  数据范围 :1<=n<=1e5 1<=m<=5e5

思路

  求桥,tarjan算法肯定少不了,但是当我交几发后发现没那么简单,不是直接套个模板就能解决的。总的思路:首先Tarjan边双连通 缩点,再求桥,跑一边树的直径。O(n+m)。
  缩点是为了去掉环,跑树的直径。
  第一问求桥的数量,发现几个样例的答案都比我小,很纳闷,模板肯定是不会有问题的,思考还有什么要注意的点,只有这种可能,我的答案会偏大,当出现重边的时候,去点其中一条重边不会影响连通,题目肯定是这个意思,原模板是多条重边只算一条边的,所以答案会偏大。
  如何改,归根是缩点不够彻底,没有把重边也缩成一个点。首先找出重边:if(child==fa) num++,num没有重边的情况一般是1,因为这是双向图,如果有重边自然>1。
再进行缩点,以前那个belong数组便不够用的,不好判断,因为重边可能出现在环中间,这个时候不用管,也有可能出现在桥上:

  1. 重边与重边相邻:所有重边都需要缩成一个点
  2. 重边与环相邻:重边与环缩成一个点
  3. 重边独立:这两点缩成一个点

  原模板不够用,而且bcc在每找到一个环的时候会,也就是原模板的意思是每个环是一个缩点,下一个缩点一定不一样,bcc就会变化(bcc表示当前是属于哪一个缩点)
  为解决这个问题考虑使用并查集,每找到一个环缩成一个点,重边也是,这样就解决所有的问题了。
  缩点解决后,重新建图,跑一遍tarjan求所有的桥,树的直径。为什么最后求一遍树的直径就可以了呢,很简单,因为缩点后,剩下的是一颗树,每一条边都是桥(其实记录一下新图有多少点就可以知道桥有多少(n-1),不过也没事,影响不大),所以答案就是求树上两点最长距离,就是树的直径。
  Tarjan复杂度都是O(n+m) 树的直径两遍bfs也是O(n+m),并查集复杂度O( α \alpha αm),总复杂度是O(n+ α \alpha αm),并查集压缩路径 α \alpha α很小。
  具体算法细节不解释,只提供思路。

代码:

#ifndef __SOLVER_H__
#define __SOLVER_H__

#include <bits/stdc++.h>
using namespace std;
#define maxn 100005
#define maxm 1000005

int head[maxn], num, tov[maxm], nex[maxm], len[maxn];
int ff[maxn];
bool instack[maxn];
int low[maxn], dfn[maxn];//时间戳
int sta[maxn], fa[maxn], fuben[maxn];//栈,记录父节点,记录用一个边双连通分量的所有点
int top, id, bcc;//栈顶,时间计数,边双连通分量的个数

class Solver
{
public:
    //并查集
	int find(int x)
	{
		return ff[x] == x ? x : ff[x] = find(ff[x]);
	}
	void add_edge(int from, int to, int dis)
	{
		nex[++num] = head[from];
		tov[num] = to;
		head[from] = num;
		len[num] = dis;
	}
	//边双连通分量
	void tarjan1(int u, int fa)
	{
		int i;
		int num = 0;//记录重边
		dfn[u] = low[u] = ++id;
		instack[u] = 1;
		sta[++top] = u;
		for (i = head[u]; i != -1; i = nex[i])
		{
			int v = tov[i];
			if (v == fa)
			{
				num++;
				continue;
			}
			if (!dfn[v])
			{
				tarjan1(v, u);
				low[u] = min(low[v], low[u]);
			}
			else if (instack[v])
				low[u] = min(dfn[v], low[u]);
		}

		if (num>1)//存在重边
		{
			int x = find(u), y = find(fa);
			if (x != y)
				ff[x] = y;
		}

		if (dfn[u] == low[u])//对环缩点,如果是单点也不影响ff[u]=u;
		{
			++bcc;
			int fu = find(u);
			do
			{
				i = sta[top--];
				int fv = find(i);
				if (fu != fv)
					ff[fv] = fu;
				instack[i] = 0;
			} while (i != u);
		}
	}

	int ind, ans1, ans2;
	void tarjan2(int cur, int fa)//cur是当前节点,fa是他的父亲
	{
		int child;
		dfn[cur] = ++ind;//计算当前节点的时间戳
		low[cur] = dfn[cur];//当前可以访问到的最早时间戳肯定是自己的时间戳
		for (int i = head[cur]; i != -1; i = nex[i]) //遍历cur的所有出点
		{
			child = tov[i];
			if (dfn[child] && child != fa)
				low[cur] = min(low[cur], dfn[child]);//如果访问到了不是父亲节点的节点,更新low的值
			if (!dfn[child])//如果这个节点之前没有被访问过
			{
				tarjan2(child, cur);//进行一次dfs过程

				if (dfn[cur]<low[child])
					ans1++;
				low[cur] = min(low[cur], low[child]);//更新low的值
			}
		}
	}

	int dis[maxn], Max, t;
	bool vis[maxn];
	int bfs(int st)
	{
		Max = 0, t = st;
		memset(dis, 0, sizeof(dis));
		memset(vis, false, sizeof(vis));
		queue<int>q;
		q.push(st);
		while (!q.empty())
		{
			int u = q.front();
			vis[u] = true;
			q.pop();
			for (int i = head[u]; i != -1; i = nex[i])
			{
				int v = tov[i], di = len[i];
				if (!vis[v])
				{
					dis[v] = dis[u] + di;
					q.push(v);
					if (dis[v]>Max)
					{
						Max = dis[v];
						t = v;
					}
				}
			}
		}
	}

	int get_max(int st)//两遍bfs求树的直径
	{
		bfs(st);
		bfs(t);
		return Max;
	}

	void init1()
	{
		memset(head, -1, sizeof(head));
		num = 0;
		memset(instack, 0, sizeof(instack));
		memset(low, 0, sizeof(low));
		memset(dfn, 0, sizeof(dfn));
		memset(sta, 0, sizeof(sta));
		memset(fa, 0, sizeof(fa));
		memset(fuben, 0, sizeof(fuben));
		top = 0, id = 0, bcc = 0;
	}

	void init2()
	{
		memset(head, -1, sizeof(head));
		num = 0;
		memset(low, 0, sizeof(low));
		memset(dfn, 0, sizeof(dfn));
		ind = 0;
		ans1 = 0, ans2 = 0;
		memset(dis, 0, sizeof(dis));
		memset(vis, 0, sizeof(vis));
	}
public:
	pair<int, int> solve(int n, const vector<pair<int, int> > &edges)
	{
		/********** Begin **********/
		for (int i = 1; i <= n; i++)
			ff[i] = i;
		init1();
		for (int i = 0; i<edges.size(); i++)
		{
			int u = edges[i].first, v = edges[i].second;
			add_edge(u, v, 0);
			add_edge(v, u, 0);
		}

		for (int i = 1; i <= n; i++)//缩点
			if (!dfn[i])
				tarjan1(i, i);
				
		init2();
		int st = 1;
		for (int i = 0; i<edges.size(); i++)//重新建图
		{
			int u = edges[i].first, v = edges[i].second;
			int fu = find(u), fv = find(v);
			if (fu != fv)
			{
				st = fu;
				add_edge(fu, fv, 1);
				add_edge(fv, fu, 1);
			}
		}

		for (int i = 1; i <= n; i++)//求桥,在前面直接记录点的个数也可以
			if (!dfn[find(i)])
				tarjan2(find(i), find(i));

		ans2 = get_max(find(st));//求树的直径
		pair<int, int>ans = make_pair(ans1, ans2);
		return ans;
		/********** End **********/
	}
};

#endif

int main()
{
	int n, m;
	while (cin >> n >> m)
	{
		vector<pair<int, int> >edge;
		for (int i = 0; i<m; i++)
		{
			int x, y;
			cin >> x >> y;
			edge.push_back(make_pair(x, y));
		}

		Solver sol;
		pair<int, int> ans = sol.solve(n, edge);
		cout << ans.first << " " << ans.second << endl;
	}
	return 0;
}


  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值