拓扑排序

\(\text{poj-4084}\)

给定一个有 \(n\) 个节点 \(m\) 条边的有向图,求出该图字典序最小的拓扑排序。

\(1 \le n \le 100\)\(1 \le m \le 500\)


拓扑排序板子。

法一:

比较好写的一种求法,直接模拟求即可,每次扫描入度为 \(0\) 的点输出。

但是时间复杂度最坏是 \(O(n^2)\) 的。

#include<iostream>
#include<cstdio>
#include<vector>
using namespace std;
#define MAXN 105
#define INF 0x3f3f3f3f

long long read() {
    long long x = 0, f = 1;
    char c = getchar();
    while(c < 48 || c > 57) { if(c == 45) f = -1; c = getchar(); }
    while(c >= 48 && c <= 57) { x = (x << 3) + (x << 1) + (c - 48); c = getchar(); }
    return x * f;
}

vector<long long> v[MAXN];
long long n, m, in[MAXN];

int main() {
	n = read(), m = read();
	for(int i = 1; i <= m; i ++) {
		long long x = read(), y = read();
		v[x].push_back(y), in[y] ++;
	}
	for(int i = 1; i <= n; i ++) if(!in[i]) {
		cout << "v" << i << " ";
		for(auto y : v[i]) in[y] --;
		in[i] = INF, i = 0;
	}
	cout << "\n";
    return 0;
}

法二:

\(\text{dfs}\) 求解,不需要记录入度,但求字典序最小的拓扑排序不太好处理。

时间复杂度为 \(O(n+m)\)

下面的代码未实现求字典序最小的拓扑排序,但可以求出一种合法的拓扑排序。

#include<iostream>
#include<cstdio>
#include<vector>
#include<stack>
using namespace std;
#define MAXN 505
#define INF 0x3f3f3f3f

long long read() {
    long long x = 0, f = 1;
    char c = getchar();
    while(c < 48 || c > 57) { if(c == 45) f = -1; c = getchar(); }
    while(c >= 48 && c <= 57) { x = (x << 3) + (x << 1) + (c - 48); c = getchar(); }
    return x * f;
}

vector<long long> v[MAXN];
stack<long long> ans;
long long n, m;
bool vis[MAXN];

void dfs(long long x) {
	for(auto y : v[x]) if(!vis[y]) dfs(y);
	vis[x] = true, ans.push(x); return;
}

int main() {
	n = read(), m = read();
	for(int i = 1; i <= m; i ++) {
		long long x = read(), y = read();
		v[x].push_back(y);
	}
	for(int i = 1; i <= n; i ++) if(!vis[i]) dfs(i);
	while(!ans.empty()) cout << ans.top() << " ";
    return 0;
}

法三:

\(\text{bfs}\) 求解,实际上也是模拟,但稍微优化了点。

下面的代码未实现求字典序最小的拓扑排序,但可以求出一种合法的拓扑排序。

实际上要求字典序的话改成优先队列就好了。

#include<iostream>
#include<cstdio>
#include<vector>
#include<queue>
using namespace std;
#define MAXN 105

long long read() {
    long long x = 0, f = 1;
    char c = getchar();
    while(c < 48 || c > 57) { if(c == 45) f = -1; c = getchar(); }
    while(c >= 48 && c <= 57) { x = (x << 3) + (x << 1) + (c - 48); c = getchar(); }
    return x * f;
}

long long n, m, in[MAXN];
vector<long long> v[MAXN];
bool vis[MAXN];

void bfs(long long s) {
	queue<long long> q;
	q.push(s);
	while(!q.empty()) {
		long long x = q.front(); q.pop();
		if(vis[x]) continue;
		vis[x] = true; cout << "v" << x << " ";
		for(auto y : v[x]) if(!(-- in[y])) q.push(y);
	}
	return;
}

int main() {
	n = read(), m = read();
	for(int i = 1; i <= m; i ++) {
		long long x = read(), y = read();
		v[x].push_back(y), in[y] ++;
	}
	for(int i = 1; i <= n; i ++) 
		if(!in[i] && !vis[i]) bfs(i);
	cout << "\n";
    return 0;
}

\(\text{hdu-3342}\)

给定一个有 \(n\) 个节点 \(m\) 条边的有向图,判断该图是否存在拓扑排序。

\(2 \le n,m \le 100\)


处理完拓扑排序扫一遍入度数组即可。

如果是法二的话,看 \(ans\) 栈中元素是否为 \(n\) 即可。

\(\text{hdu-1285}\)

给定一个有 \(n\) 个节点 \(m\) 条边的有向图,求出该图字典序最小的拓扑排序。

\(1 \le n \le 500\)


\(\text{poj-4084}\) 一摸一样,但是注意!有多组数据并且行末无空格,很恶心。

\(\text{hdu-2647}\)

\(n\) 名工人,将对他们进行奖励,但奖励有 \(m\) 条限制,要求 \(x_i\)\(y_i\) 的奖励多,且每名工人至少奖励 \(888\) 元,求最少的奖励总额。

\(1 \le n \le 10^4\)\(1 \le m \le 2 \times 10^4\)


考虑反向建图,这样就不会存在过程中的影响。

拓扑排序过程中,\(x_i\) 只需要比 \(y_i\)\(1\) 即可,记一个 \(cnt_i\) 数组,过程中累加。

最后答案加上 \(888n\) 即可。

#include<iostream>
#include<cstdio>
#include<vector>
#include<queue>
using namespace std;
#define MAXN 10005

long long read() {
    long long x = 0, f = 1;
    char c = getchar();
    while(c < 48 || c > 57) { if(c == 45) f = -1; c = getchar(); }
    while(c >= 48 && c <= 57) { x = (x << 3) + (x << 1) + (c - 48); c = getchar(); }
    return x * f;
}

long long n, m, ans, in[MAXN], cnt[MAXN];
vector<long long> v[MAXN];

bool topu() {
	queue<long long> q;
	long long res = 0;
	for(int i = 1; i <= n; i ++) if(!in[i]) q.push(i);
	while(!q.empty()) {
		long long x = q.front(); q.pop(); res ++;
		for(auto y : v[x]) {
			if(!(-- in[y])) q.push(y);
			cnt[y] = cnt[x] + 1;
		}
	}
	if(res == n) return true;
	return false;
}

int main() {
	while(cin >> n >> m) {
		for(int i = 1; i <= n; i ++) 
			v[i].clear(), in[i] = cnt[i] = 0;
		for(int i = 1; i <= m; i ++) {
			long long x = read(), y = read();
			v[y].push_back(x), in[x] ++;
		}
		if(topu()) {
			ans = 0;
			for(int i = 1; i <= n; i ++) ans += cnt[i];
			cout << ans + 888 * n << "\n";
		}
		else cout << "-1\n";
	}
    return 0;
}

\(\text{hdu-4857}\)

\(n\) 个人要逃生,同时有 \(m\) 个约束条件 \((x_i, y_i)\),表示 \(x_i\) 要在 \(y_i\) 之前逃生。

同时,由于 \(1\) 号给的钱最多,\(2\) 号其次,以此类推,所以有多种逃生方式时,要求尽可能的让 \(1\) 靠前,如果此时还有多种情况,让 \(2\) 号尽可能靠前,以此类推。

求出最优的排序,保证有解。

\(1 \le T \le 5\)\(1 \le n \le 3 \times 10^4\)\(1 \le m \le 10^5\)


这道题并非简单的要求字典序最小,例如下面这种情况。

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

答案并非 3 5 2 4 6 1 7,而是 6 1 5 2 3 4 7,因为后者 \(1\) 比前者更靠前,且两种排序均合法。

因此我们考虑反向建边,在反图上求字典序最大的拓扑序,倒序输出即可。

#include<iostream>
#include<cstdio>
#include<vector>
#include<queue>
using namespace std;
#define MAXN 30005

long long read() {
    long long x = 0, f = 1;
    char c = getchar();
    while(c < 48 || c > 57) { if(c == 45) f = -1; c = getchar(); }
    while(c >= 48 && c <= 57) { x = (x << 3) + (x << 1) + (c - 48); c = getchar(); }
    return x * f;
}

long long T, n, m, in[MAXN], ans[MAXN], cnt;
vector<long long> v[MAXN];

void topu() {
	priority_queue<long long> q;
	for(int i = 1; i <= n; i ++) if(!in[i]) q.push(i);
	while(!q.empty()) {
		long long x = q.top(); q.pop(); 
		ans[++ cnt] = x;
		for(auto y : v[x]) if(!(-- in[y])) q.push(y);
	}
	return;
}

int main() {
	T = read();
	while(T --) {
		n = read(), m = read(), cnt = 0;
		for(int i = 1; i <= n; i ++) v[i].clear(), in[i] = 0;
		for(int i = 1; i <= m; i ++) {
			long long x = read(), y = read();
			v[y].push_back(x), in[x] ++;
		}
		topu();
		for(int i = cnt; i >= 1; i --) cout << ans[i] << " ";
		cout << "\n";
	}
    return 0;
}

\(\text{poj-1128}\)

有若干个矩形画框,后放上的画框会覆盖之前的画框,给出最后的效果,输出所有可能的顺序。

画框有以下限制:

  1. 画框的宽度始终恰好为 \(1\) 个字符,且两侧的长度从不短于 \(3\) 个字符。

  2. 至少可以看到每个画框四个侧面的一个部分。一个角落显示两个侧面。

  3. 画框将用大写字母标记,且没有两个画框会被分配相同的字母。

下面是一种可能的效果,放置顺序为 EDABC

.CCC....
ECBCBB..
DCBCDB..
DCCC.B..
D.B.ABAA
D.BBBB.A
DDDDAD.A
E...AAAA
EEEEEE..

\(1 \le h,w \le 30\)


本质上就是转化为求所有拓扑序,简单处理一下各个画框的范围即可,细节有点多。

#include<iostream>
#include<cstdio>
#include<vector>
#include<set>
using namespace std;
#define MAXN 35

long long h, w, in[MAXN], xi[MAXN], yi[MAXN], xx[MAXN], yx[MAXN], cnt;
char s[MAXN][MAXN], ans[MAXN];
vector<long long> v[MAXN];
bool vis[MAXN];

void dfs(long long x, long long p) {
	ans[p] = char(x + 'A'), vis[x] = true;
	if(p == cnt) {
		for(int i = 1; i <= cnt; i ++) cout << ans[i];
		cout << "\n"; vis[x] = false; return;
	}
	for(auto y : v[x]) in[y] --;
	for(int i = 0; i < 26; i ++) 
		if(xi[i] != MAXN && !vis[i] && !in[i]) dfs(i, p + 1);
	for(auto y : v[x]) in[y] ++; vis[x] = false;
	return;
}

int main() {
	ios::sync_with_stdio(0);
	cin.tie(0), cout.tie(0);
	while(cin >> h >> w) {
		for(int i = 1; i <= h; i ++) cin >> (s[i] + 1);
		for(int i = 0; i < 26; i ++) 
			xi[i] = yi[i] = MAXN, xx[i] = yx[i] = -1, 
			v[i].clear(), in[i] = 0;
		cnt = 0;
		for(int i = 1; i <= h; i ++) for(int j = 1; j <= w; j ++) {
			if(s[i][j] == '.') continue;
			long long c = s[i][j] - 'A';
			xi[c] = min(xi[c], 1ll * i), yi[c] = min(yi[c], 1ll * j);
			xx[c] = max(xx[c], 1ll * i), yx[c] = max(yx[c], 1ll * j);
		}
		for(int k = 0; k < 26; k ++) if(xi[k] != MAXN) {
			set<long long> st;
			for(int i = xi[k]; i <= xx[k]; i ++) {
				long long p = s[i][yi[k]] - 'A', q = s[i][yx[k]] - 'A';
				if(p != k) st.insert(p);
				if(q != k) st.insert(q);
			}
			for(int i = yi[k]; i <= yx[k]; i ++) {
				long long p = s[xi[k]][i] - 'A', q = s[xx[k]][i] - 'A';
				if(p != k) st.insert(p);
				if(q != k) st.insert(q);
			}
			for(auto x : st) v[k].push_back(x), in[x] ++;
		}
		for(int i = 0; i < 26; i ++) if(xi[i] != MAXN) cnt ++;
		for(int i = 0; i < 26; i ++) if(xi[i] != MAXN && !in[i]) dfs(i, 1);
	}
    return 0;
}

\(\text{hdu-1811}\)

给定 \(n\) 个数的大小关系,判断出通过这 \(m\) 个关系是否能确定所有数唯一的相对大小关系。

若信息有冲突输出 CONFLICT,否则若信息不完全输出 UNCERTAIN,合法输出 OK

\(0 \le n \le 10^4\)\(0 \le m \le 2 \times 10^4\)


显然 UNCERTAIN 的条件为同时存在至少两个入度为 \(0\) 的点,若原图有环则答案为 CONFLICT

需要注意两个不合法情况的优先级。

#include<iostream>
#include<cstdio>
#include<vector>
#include<queue>
using namespace std;
#define MAXN 20005

struct node { long long x, y; char c; } a[MAXN];
long long n, m, cnt, in[MAXN], fa[MAXN];
vector<long long> v[MAXN];
bool fg = false;

long long find(long long x) { return (fa[x] == x) ? x : fa[x] = find(fa[x]); }

void topu() {
	queue<long long> q; fg = false;
	for(int i = 1; i <= n; i ++) if(!in[i] && fa[i] == i) q.push(i);
	while(!q.empty()) {
		if(q.size() > 1) fg = true;
		long long x = q.front(); q.pop(); cnt --;
		for(auto y : v[x]) if(!(-- in[y])) q.push(y);
	}
	if(cnt > 0) cout << "CONFLICT\n";
	else if(fg) cout << "UNCERTAIN\n";
	else cout << "OK\n";
	return;
}

int main() {
	ios::sync_with_stdio(0);
	cin.tie(0), cout.tie(0);
	while(cin >> n >> m) {
		cnt = n;
		for(int i = 1; i <= n; i ++) 
			fa[i] = i, in[i] = 0, v[i].clear();
		for(int i = 1; i <= m; i ++) {
			cin >> a[i].x >> a[i].c >> a[i].y;
			a[i].x ++, a[i].y ++;
			if(a[i].c == '=') {
				long long fx = find(a[i].x), fy = find(a[i].y);
				if(fx == fy) continue;
				fa[fy] = fx, cnt --;
			} 
		}
		for(int i = 1; i <= m; i ++) {
			if(a[i].c == '=') continue;
			long long fx = find(a[i].x), fy = find(a[i].y);
			if(a[i].c == '>') v[fx].push_back(fy), in[fy] ++;
			else v[fy].push_back(fx), in[fx] ++;
		}
		topu();
	}
    return 0;
}
posted @ 2025-12-08 21:47  So_noSlack  阅读(7)  评论(0)    收藏  举报