算法课复习 -- 图、拓扑序、SCC

HDU #1232 : 畅通工程

传送门:http://acm.hdu.edu.cn/showproblem.php?pid=1232

题意:给出n个点m条边,问至少还需要添加多少条边才能使整个图连通。

思路:对没有访问过的点进行dfs,每次dfs中的一团为一个连通块,答案即为“连通块数量-1”。

AC代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<cstdlib>
#include<utility>
#include<algorithm>
#include<utility>
#include<queue>
#include<vector>
#include<set>
#include<stack>
#include<cmath>
#include<map>
#include<ctime>
#include<functional>
#include<bitset>
#define P pair<int,int>
#define ll long long
#define ull unsigned long long
#define lson id*2,l,mid
#define rson id*2+1,mid+1,r
#define ls id*2
#define rs (id*2+1)
#define Mod(a,b) a<b?a:a%b+b
#define cl0(a) memset(a,0,sizeof(a))
#define cl1(a) memset(a,-1,sizeof(a))
using namespace std;

const ll M = 1e9 + 7;
const ll INF = 2e9 + 10;
const int N = 410;
const double _e = 10e-6;
const int maxn = 1e3 + 10;
const int matSize = 9;
const int dx[4] = { 0,0,1,-1 }, dy[4] = { 1,-1,0,0 };
const int _dx[8] = { -1,-1,-1,0,0,1,1,1 }, _dy[8] = { -1,0,1,-1,1,-1,0,1 };

int x, y;

int n, m;
bool used[maxn][maxn], vis[maxn];
vector<int> G[maxn];

void dfs(int u)
{
	vis[u] = true;
	for (int i = 0; i < G[u].size(); i++) {
		int v = G[u][i];
		if (!vis[v])
			dfs(v);
	}
}

int main()
{
	while (~scanf("%d", &n)) {
		if (n == 0)break;
		cl0(used); cl0(vis);
		for (int i = 1; i <= 1000; i++)
			G[i].clear();
		scanf("%d", &m);
		while (m--) {
			scanf("%d%d", &x, &y);
			if (!used[x][y]) {
				G[x].push_back(y);
				G[y].push_back(x);
				used[x][y] = 1; used[y][x] = 1;
			}
		}
		int ans = 0;
		for (int i = 1; i <= n; i++) {
			if (!vis[i]) {
				dfs(i);
				ans++;
			}
		}
		printf("%d\n", ans - 1);
	}
	return 0;
}

 

hihoCoder #1174 : 拓扑排序·一

传送门:https://hihocoder.com/problemset/problem/1174

题意:有n节课m个关系,对于每个关系a b表示a是b的前置课程,问课程安排有没有问题,也就是有没有环。

思路:记录每个点的入度,入度为0的放入队列。每次从队列取出点时将该点所有后置课程的入度-1,若后置课程入度为0则放入队列。当队列为空时结束,判断这时是否还有点的入度>0,如果有则有问题。

AC代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<cstdlib>
#include<utility>
#include<algorithm>
#include<utility>
#include<queue>
#include<vector>
#include<set>
#include<stack>
#include<cmath>
#include<map>
#include<ctime>
#include<functional>
#include<bitset>
#define P pair<int,int>
#define ll long long
#define ull unsigned long long
#define lson id*2,l,mid
#define rson id*2+1,mid+1,r
#define ls id*2
#define rs (id*2+1)
#define Mod(a,b) a<b?a:a%b+b
#define cl0(a) memset(a,0,sizeof(a))
#define cl1(a) memset(a,-1,sizeof(a))
using namespace std;

const ll M = 1e9 + 7;
const ll INF = 2e9 + 10;
const int N = 410;
const double _e = 10e-6;
const int maxn = 1e5 + 10;
const int matSize = 9;
const int dx[4] = { 0,0,1,-1 }, dy[4] = { 1,-1,0,0 };
const int _dx[8] = { -1,-1,-1,0,0,1,1,1 }, _dy[8] = { -1,0,1,-1,1,-1,0,1 };

int x, y;

int n, m;
int in[maxn];
vector<int> G[maxn];

int main()
{
	int t;
	scanf("%d", &t);
	while (t--) {
		cl0(in);
		for (int i = 1; i <= 100000; i++)
			G[i].clear();
		scanf("%d%d", &n, &m);
		while (m--) {
			scanf("%d%d", &x, &y);
			G[x].push_back(y);
			in[y]++;
		}
		queue<int> que;
		while (!que.empty())que.pop();
		for (int i = 1; i <= n; i++)
			if (in[i] == 0)que.push(i);
		while (!que.empty()) {
			int u = que.front(); que.pop();
			for (int i = 0; i < G[u].size(); i++) {
				int v = G[u][i];
				in[v]--;
				if (in[v] == 0) 
					que.push(v);
			}
		}
		bool judge = true;
		for (int i = 1; i <= n; i++) {
			if (in[i] > 0) {
				judge = false;
				break;
			}
		}
		if (judge)
			puts("Correct");
		else
			puts("Wrong");
	}
	return 0;
}

 

POJ #3249 : Test for Job

传送门:http://poj.org/problem?id=3249

题意:有n个点m条边,每个点有一个权值,问从一个入度为0的点沿边走到出度为0的点,最大的权值和是多少。

思路:拓扑同前一道题。维护点的最大权值和,sum[v]=max(sum[v],sum[u]+c[v])。当点的出度为0时,ans=max(ans,sum[u])。

AC代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<cstdlib>
#include<utility>
#include<algorithm>
#include<utility>
#include<queue>
#include<vector>
#include<set>
#include<stack>
#include<cmath>
#include<map>
#include<ctime>
#include<functional>
#include<bitset>
#define P pair<int,int>
#define ll long long
#define ull unsigned long long
#define lson id*2,l,mid
#define rson id*2+1,mid+1,r
#define ls id*2
#define rs (id*2+1)
#define Mod(a,b) a<b?a:a%b+b
#define cl0(a) memset(a,0,sizeof(a))
#define cl1(a) memset(a,-1,sizeof(a))
using namespace std;

const ll M = 1e9 + 7;
const ll INF = 2e9 + 10;
const int N = 410;
const double _e = 10e-6;
const int maxn = 1e5 + 10;
const int matSize = 9;
const int dx[4] = { 0,0,1,-1 }, dy[4] = { 1,-1,0,0 };
const int _dx[8] = { -1,-1,-1,0,0,1,1,1 }, _dy[8] = { -1,0,1,-1,1,-1,0,1 };

int x, y;

int n, m;
vector<int> G[maxn];
int c[maxn];
ll ans[maxn];
int in[maxn];

int main()
{
	while (~scanf("%d%d", &n, &m)) {
		cl0(in); 
		for (int i = 1; i <= 100000; i++) {
			ans[i] = -INF;
			G[i].clear();
		}
		for (int i = 1; i <= n; i++)
			scanf("%d", &c[i]);
		while (m--) {
			scanf("%d%d", &x, &y);
			G[x].push_back(y);
			in[y]++;
		}
		queue<int> que;
		for (int i = 1; i <= n; i++) {
			if (in[i] == 0) {
				que.push(i);
				ans[i] = c[i];
			}
		}
		ll anss = -INF;
		while (!que.empty()) {
			int u = que.front(); que.pop();
			if (G[u].size() == 0)
				anss = max(anss, ans[u]);
			for (int i = 0; i < G[u].size(); i++) {
				int v = G[u][i];
				in[v]--;
				ans[v] = max(ans[v], ans[u] + c[v]);
				if (in[v] == 0)
					que.push(v);
			}
		}
		printf("%lld\n", anss);
	}
	return 0;
}

 

HDU #1269 : 迷宫城堡

传送门:http://acm.hdu.edu.cn/showproblem.php?pid=1269

题意:求SCC(强连通分量)的个数是否为1。

思路:两种方法

第一种:直接tarjan求强连通分量。tarjan算法可以参考这篇:https://blog.csdn.net/qq_34374664/article/details/77488976

第二种:一共进行2次dfs。①在逆图上求拓扑序记录到栈(dfs,在点退出时入栈);②将点从栈按顺序取出,若没访问过则进行dfs并且这一团为一个强连通分量。

AC代码:

//第一种:tarjan
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<cstdlib>
#include<utility>
#include<algorithm>
#include<utility>
#include<queue>
#include<vector>
#include<set>
#include<stack>
#include<cmath>
#include<map>
#include<ctime>
#include<functional>
#include<bitset>
#define P pair<int,int>
#define ll long long
#define ull unsigned long long
#define lson id*2,l,mid
#define rson id*2+1,mid+1,r
#define ls id*2
#define rs (id*2+1)
#define Mod(a,b) a<b?a:a%b+b
#define cl0(a) memset(a,0,sizeof(a))
#define cl1(a) memset(a,-1,sizeof(a))
using namespace std;

const ll M = 1e9 + 7;
const ll INF = 1e9;
const int N = 410;
const double _e = 10e-6;
const int maxn = 1e4 + 10;
const int matSize = 9;
const int dx[4] = { 0,0,1,-1 }, dy[4] = { 1,-1,0,0 };
const int _dx[8] = { -1,-1,-1,0,0,1,1,1 }, _dy[8] = { -1,0,1,-1,1,-1,0,1 };

int x, y;

int n, m;
vector<int> G[maxn];
int no_sc, tot; int dfn[maxn], low[maxn], scc[maxn];
stack <int> s;

void tarjan(int u)
{
	dfn[u] = low[u] = ++tot; 
	s.push(u);
	for (int i : G[u]) {
		if (dfn[i] == 0) {
			tarjan(i);
			low[u] = min(low[u], low[i]);
		}
		else if (scc[i] == 0)
			low[u] = min(low[u], dfn[i]);
	}
	if (dfn[u] == low[u]) {
		no_sc++;
		while (1) {
			int x = s.top(); s.pop();
			scc[x] = no_sc; 
			if (x == u) break;
		}
	}
}

int main()
{
	while (~scanf("%d%d", &n, &m)) {
		if (n == 0 && m == 0)break;
		no_sc = 0;
		cl0(dfn); cl0(low); cl0(scc);
		for (int i = 1; i <= 10000; i++)
			G[i].clear();
		while (m--) {
			scanf("%d%d", &x, &y);
			G[x].push_back(y);
		}
		for (int i = 1; i <= n; i++)
			if (dfn[i] == 0) 
				tarjan(i);
		if (no_sc == 1)
			puts("Yes");
		else
			puts("No");
	}
	return 0;
}
//第二种:2次dfs
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<cstdlib>
#include<utility>
#include<algorithm>
#include<utility>
#include<queue>
#include<vector>
#include<set>
#include<stack>
#include<cmath>
#include<map>
#include<ctime>
#include<functional>
#include<bitset>
#define P pair<int,int>
#define ll long long
#define ull unsigned long long
#define lson id*2,l,mid
#define rson id*2+1,mid+1,r
#define ls id*2
#define rs (id*2+1)
#define Mod(a,b) a<b?a:a%b+b
#define cl0(a) memset(a,0,sizeof(a))
#define cl1(a) memset(a,-1,sizeof(a))
using namespace std;

const ll M = 1e9 + 7;
const ll INF = 1e9;
const int N = 410;
const double _e = 10e-6;
const int maxn = 1e4 + 10;
const int matSize = 9;
const int dx[4] = { 0,0,1,-1 }, dy[4] = { 1,-1,0,0 };
const int _dx[8] = { -1,-1,-1,0,0,1,1,1 }, _dy[8] = { -1,0,1,-1,1,-1,0,1 };

int x, y;

int n, m;
vector<int> G[maxn], H[maxn];
stack <int> s;
bool used[maxn];

void topsort(int u)
{
	used[u] = true;
	for (int i = 0; i < H[u].size(); i++) {
		int v = H[u][i];
		if(!used[v])
			topsort(v);
	}
        s.push(u); 
}

void dfs(int u)
{
	used[u] = true;
	for (int i = 0; i < G[u].size(); i++) {
		int v = G[u][i];
		if (!used[v])
			dfs(v);
	}
}

int main()
{
	while (~scanf("%d%d", &n, &m)) {
		if (n == 0 && m == 0)break;		
		for (int i = 1; i <= 10000; i++) {
			G[i].clear();
			H[i].clear();
		}
		while (!s.empty())s.pop();
		while (m--) {
			scanf("%d%d", &x, &y);
			G[x].push_back(y);
			H[y].push_back(x);
		}
		cl0(used);
		for (int i = 1; i <= n; i++) {
			if (!used[i])topsort(i);
		}
		cl0(used);
		int cnt = 0;
		while (!s.empty()) {
			int u = s.top(); s.pop();
			if (!used[u]) {
				dfs(u);
				cnt++;
			}
		}
		if (cnt == 1)
			puts("Yes");
		else
			puts("No");
	}
	return 0;
}

 

POJ #2762 : Going from u to v or from v to u?

传送门:http://poj.org/problem?id=2762

题意:给一个n个点m条边的有向图。问是否能满足对于任意两点u v,使得从u能到v或从v能到u。

思路:先求强连通分量,以强连通分量为点建新图,新图是个DAG(有向无环图)。

由于每一块强连通分量内部是可以两两到达的,因此只要考虑新图上的点是否能从u到v或从v到u。

若要满足条件,新图必须为一条链。这里是利用拓扑来求的,当队列里同时出现>1个点时就为“No”。最后拓扑完后判断是否每个点都用过了,如果有点没用过的话也为“No”。

AC代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<cstdlib>
#include<utility>
#include<algorithm>
#include<utility>
#include<queue>
#include<vector>
#include<set>
#include<stack>
#include<cmath>
#include<map>
#include<ctime>
#include<functional>
#include<bitset>
#define P pair<int,int>
#define ll long long
#define ull unsigned long long
#define lson id*2,l,mid
#define rson id*2+1,mid+1,r
#define ls id*2
#define rs (id*2+1)
#define Mod(a,b) a<b?a:a%b+b
#define cl0(a) memset(a,0,sizeof(a))
#define cl1(a) memset(a,-1,sizeof(a))
using namespace std;

const ll M = 1e9 + 7;
const ll INF = 2e9 + 10;
const int N = 410;
const double _e = 10e-6;
const int maxn = 1e3 + 10;
const int matSize = 9;
const int dx[4] = { 0,0,1,-1 }, dy[4] = { 1,-1,0,0 };
const int _dx[8] = { -1,-1,-1,0,0,1,1,1 }, _dy[8] = { -1,0,1,-1,1,-1,0,1 };

int x, y;

int n, m;
vector<int> G[maxn], H[maxn];
int in[maxn];
bool used[maxn][maxn], vis[maxn];
int no_sc, tot; int dfn[maxn], low[maxn], scc[maxn];
stack <int> s;

void tarjan(int u)
{
	dfn[u] = low[u] = ++tot;
	s.push(u);
	for (int i = 0; i < G[u].size(); i++) {
		int v = G[u][i];
		if (dfn[v] == 0) {
			tarjan(v);
			low[u] = min(low[u], low[v]);
		}
		else if (scc[v] == 0)
			low[u] = min(low[u], dfn[v]);
	}
	if (dfn[u] == low[u]) {
		no_sc++;
		while (1) {
			int x = s.top(); s.pop();
			scc[x] = no_sc;
			if (x == u) break;
		}
	}
}

int main()
{
	int t;
	scanf("%d", &t);
	while (t--) {
		scanf("%d%d", &n, &m);
		no_sc = 0;
		cl0(dfn); cl0(low); cl0(scc); 
		cl0(used); cl0(vis); cl0(in);
		for (int i = 1; i <= 1000; i++) {
			G[i].clear();
			H[i].clear();
		}
		while (m--) {
			scanf("%d%d", &x, &y);
			G[x].push_back(y);
		}
		for (int i = 1; i <= n; i++)
			if (dfn[i] == 0)
				tarjan(i);
		
		for (int i = 1; i <= n; i++) {
			for (int j = 0; j < G[i].size(); j++) {
				int v = G[i][j];
				if (scc[i] != scc[v]&&!used[scc[i]][scc[v]]) {
					used[scc[i]][scc[v]] = true;
					H[scc[i]].push_back(scc[v]);
					in[scc[v]]++;
				}
			}
		}
		
		if (no_sc == 1) {
			puts("Yes");
			continue;
		}

		queue<int> que;
		while (!que.empty())
			que.pop();
		for (int i = 1; i <= no_sc; i++) {
			if (in[i] == 0) {
				que.push(i);
				vis[i] = true;
			}
		}
		if (que.size() > 1) {
			puts("No");
			continue;
		}
		bool judge = true;
		while (!que.empty()) {
			int u = que.front(); que.pop();
			for (int i = 0; i < H[u].size(); i++) {
				int v = H[u][i];
				in[v]--;
				if (in[v] == 0) {
					que.push(v);
					vis[v] = true;
				}
			}
			if (que.size() > 1) {
				puts("No");
				judge = false;
				break;
			}
		}
		if (judge) {
			for (int i = 1; i <= no_sc; i++) {
				if (!vis[i]) {
					judge = false;
					break;
				}
			}
			if (judge)
				puts("Yes");
			else
				puts("No");
		}
	}
	return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值