程序设计思维 A - TT 的魔法猫(Floyd算法)

题目

众所周知,TT 有一只魔法猫。
这一天,TT 正在专心致志地玩《猫和老鼠》游戏,然而比赛还没开始,聪明的魔法猫便告诉了 TT 比赛的最终结果。TT 非常诧异,不仅诧异于他的小猫咪居然会说话,更诧异于这可爱的小不点为何有如此魔力?
魔法猫告诉 TT,它其实拥有一张游戏胜负表,上面有 N 个人以及 M 个胜负关系,每个胜负关系为 A B,表示 A 能胜过 B,且胜负关系具有传递性。即 A 胜过 B,B 胜过 C,则 A 也能胜过 C。
TT 不相信他的小猫咪什么比赛都能预测,因此他想知道有多少对选手的胜负无法预先得知,你能帮帮他吗?

Input
第一行给出数据组数。
每组数据第一行给出 N 和 M(N , M <= 500)。
接下来 M 行,每行给出 A B,表示 A 可以胜过 B。

Output
对于每一组数据,判断有多少场比赛的胜负不能预先得知。注意 (a, b) 与 (b, a) 等价,即每一个二元组只被计算一次。

Sample Input
3
3 3
1 2
1 3
2 3
3 2
1 2
2 3
4 2
1 2
3 4

Sample Output
0
0
4

思路

这题是一个Floyd算法的变形。
一、Floyd算法
Floyd算法是一种动态规划算法,可以求图中任意两点的最短(或最长)路径。使用Floyd算法要用到两个二维数组:
dis[i][j]:从点i到点j的距离
via[i][j] = k:从点i到点j要经过点k
初始时,若从点i到点j有条长度(或权重)为w的边,则dis[i][j] = w,否则dis[i][j] = +∞。然后是Floyd算法主体,只有3重循环:

for (int k = 1; k <= N; k++) {
	for (int i = 1; i <= N; i++) {
		for (int j = 1; j <= N; j++) {
			if (dis[i][j] > dis[i][k] + dis[k][j]) {
				dis[i][j] = dis[i][k] + dis[k][j];
				via[i][j] = k;
			}
		}
	}
}

二、解题
将Floyd算法稍微变形一下:
dis[i][j] = true:i可以胜过j
Floyd算法主体:

for (int k = 1; k <= N; k++) {
	for (int i = 1; i <= N; i++) {
		if (!dis[i][k]) continue;	// 剪枝
		for (int j = 1; j <= N; j++) {
			if (dis[k][j]) dis[i][j] = true;
		}
	}
}

这样就可以很好地解决了。

代码

#include <iostream>
#define MAX 550

using namespace std;

int N, M, r;

int cnt;

bool dis[MAX][MAX];
//int f[MAX][MAX];

void init()
{
	memset(dis, false, sizeof(dis));
	for (int i = 1; i <= N; i++) dis[i][i] = true;
	//memset(f, -1, sizeof(f));
	cnt = 0;
}

void floyd()
{
	for (int k = 1; k <= N; k++) {
		for (int i = 1; i <= N; i++) {
			if (!dis[i][k]) continue;
			for (int j = 1; j <= N; j++) {
				if (dis[k][j]) dis[i][j] = true;
			}
		}
	}

	for (int i = 1; i <= N; i++) {
		for (int j = i + 1; j <= N; j++) {
			if ((!dis[i][j]) && (!dis[j][i])) cnt++;
		}
	}

}

int main()
{
	cin >> r;
	for (int i = 0; i < r; i++) {
		cin >> N >> M;

		init();

		for (int j = 0; j < M; j++) {
			int a, b;
			cin >> a >> b;

			dis[a][b] = true;
			//dis[b][a] = true;
		}

		floyd();

		cout << cnt << endl;
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值