今天学习了拓扑排序算法,做了三道相关题目。
一、1285 确定比赛名次
一道纯拓扑排序,很简单,代码中有注释。
#include <iostream>
#include <vector>
#include <queue>
#include <stdio.h>
#include <string.h>
using namespace std;
//HDU Accepted 1285 15MS 1440K 1051 B G++
int n, m, a, b;
int indegree[505];
vector<int> G[505]; //邻接表
priority_queue<int, vector<int>, greater<int> > Q; //因为输出顺序从小到大,所以使用优先队列
int main() {
while(~scanf("%d%d", &n, &m)) {
//init
memset(indegree, 0, sizeof(indegree));
for(int i=0; i<505; i++)
G[i].clear();
for(int i=0; i<m; i++) {
scanf("%d%d", &a, &b);
G[a].push_back(b);
indegree[b]++;
}
//C++中的queue自身是不支持clear操作,因此手动清空
while(!Q.empty()) Q.pop();
//统计所有节点的入度,把入度为0的加入队列Q
for(int i=1; i<=n; i++)
if(indegree[i] == 0)
Q.push(i);
bool flag = 1;
while(!Q.empty()) {
int cur = Q.top();
if(flag) {
printf("%d", cur);
flag = 0;
}
else
printf(" %d", cur);
Q.pop();
//把与节点cur相邻的节点入度减1,一共 G[cur].size()个
for(int i=0; i<G[cur].size(); i++) {
indegree[G[cur][i]]--;
if(indegree[G[cur][i]] == 0)
Q.push(G[cur][i]);
}
}
printf("\n");
}
return 0;
}
二、2647 Reward
这个题和上一题不同之处在于,它需要判断是否成环以及需要加上节点层数的信息。对于这两点,具体实现方式如下:
- 判断是否成环:引入cnt变量,每次出队就加1,如果最后不等于节点数,则说明有环
- 节点层数的信息:代码中使用cost数组记录,首先入度为0的节点,cost为0,代表第0层。在后续出队过程中,若发现有新的节点因为当前节点的出队 入度变为0了,则它的cost值为当前这个节点的cost值加1。实现的时候注意要把整个图反过来。
AC代码:
#include <iostream>
#include <vector>
#include <queue>
#include <stdio.h>
using namespace std;
//HDU Accepted 2647 31MS 1920K 1138 B G++
int n, m, a, b;
int indegree[10005];
int cost[10005];
vector<int> G[10005];
queue<int> Q;
int main() {
while(~scanf("%d%d", &n, &m)) {
//init
for(int i=0; i<10005; i++) {
G[i].clear();
indegree[i] = 0;
cost[i] = 0;
}
//input & store
for(int i=0; i<m; i++) {
scanf("%d%d", &a, &b);
//a要比b多,而我们希望初始入度为0的点获得888,第k层的节点获得888+k元 。则 b-> a
G[b].push_back(a);
indegree[a]++;
}
//清空队列
while(!Q.empty()) Q.pop();
//统计入度为0的节点
for(int i=1; i<=n; i++) {
if(indegree[i] == 0) {
Q.push(i);
cost[i] = 0;
}
}
int cnt = 0;
while(!Q.empty()) {
int cur = Q.front();
cnt++;
Q.pop();
//与cur相邻的节点入度减1
for(int i=0; i<G[cur].size(); i++) {
indegree[G[cur][i]]--;
if(indegree[G[cur][i]] == 0) {
Q.push(G[cur][i]);
cost[G[cur][i]] = cost[cur] + 1;
}
}
}
if(cnt != n) { //有环
printf("-1\n");
continue;
}
int base = 888 * n; //每个人的基础工资
int layer = 0;
for(int i=1; i<=n; i++)
layer += cost[i];
printf("%d\n", base+layer);
}
return 0;
}
三、1811 Rank of Tetris
这个题是拓扑排序和+并查集。需要我们判断给出的图是信息冲突,还是信息不全,还是可以判断名次的情况。起初不知道“=”的情况如何处理以及如果将并查集融入进去。下面是大致思路:
- 并查集就是用来处理“=”这种情况的。属于同一个父节点的节点,它们是等价的
- 信息不全:入度为0的节点多于1个就说明信息不全了。
- 信息冲突:图成环了,引入cnt变量初始化为n。若最后cnt > 0,说明冲突。
程序在读入的时候,就要先判断如果输入的是“=”情况并且它们的父节点不相同,则我们将这两个节点合并,并把cnt减1。在向邻接表中加入信息的时候,我们也需要做找祖宗节点(find)的操作,将两个节点间的信息变成祖宗间的信息。那么在第一次入队时,需要满足indegree[i] == 0 && find(i) == i
,也就是入度为0的祖宗节点才能入队。
具体AC代码如下:
#include <iostream>
#include <vector>
#include <queue>
#include <string.h>
#include <stdio.h>
using namespace std;
//HDU Accepted 1811 46MS 1944K 1576 B G++
int n, m, cnt;
int f[10005], a[20005], b[20005];
char op[20005];
int indegree[10005];
vector<int> G[10005];
int find(int x) {
if(x == f[x]) return x;
return f[x] = find(f[x]);
}
int main() {
while(~scanf("%d%d", &n, &m)) {
int cnt = n;
memset(indegree, 0, sizeof(indegree));
for(int i=0; i<10005; i++){
G[i].clear();
f[i] = i;
}
for(int i=0; i<m; i++) {
scanf("%d %c %d", &a[i], &op[i], &b[i]);
if(op[i] == '=') {
//union
int fa = find(a[i]);
int fb = find(b[i]);
if(fa != fb) {
f[fa] = fb;
cnt--;
}
}
}
//处理并查集
for(int i=0; i<m; i++) {
int fa = find(a[i]);
int fb = find(b[i]);
if(op[i] == '>') {
G[fa].push_back(fb);
indegree[fb]++;
}
if(op[i] == '<') {
G[fb].push_back(fa);
indegree[fa]++;
}
}
queue<int> Q;
while(!Q.empty()) Q.pop();
//统计所有节点的入度,把入度为0的祖宗节点加入队列Q
for(int i=0; i<n; i++)
if(indegree[i] == 0 && find(i) == i) //find(i)==i要加
Q.push(i);
bool flag = 0;
while(!Q.empty()) {
if(Q.size() > 1) {
//第一次如果队列里有多于一个元素,说明有多个入度为0的节点,也就是信息不全的情况
flag = 1;
}
int cur = Q.front();
Q.pop();
cnt--;
for(int i=0; i<G[cur].size(); i++) {
indegree[G[cur][i]]--;
if(indegree[G[cur][i]] = = 0)
Q.push(G[cur][i]);
}
}
if(cnt > 0) printf("CONFLICT\n");
else if(flag) printf("UNCERTAIN\n");
else printf("OK\n");
}
return 0;
}