P2419 [USACO08JAN] Cow Contest S

P2419 [USACO08JAN] Cow Contest S

题目描述

$ N (1 ≤ N ≤ 100) $ cows, conveniently numbered $ 1 ~ N $ , are participating in a programming contest. As we all know, some cows code better than others. Each cow has a certain constant skill rating that is unique among the competitors.

The contest is conducted in several head-to-head rounds, each between two cows. If cow $ A $ has a greater skill level than cow $ B (1 ≤ A ≤ N; 1 ≤ B ≤ N; A ≠ B) $, then cow $ A $ will always beat cow $ B $ .

Farmer John is trying to rank the cows by skill level. Given a list the results of $ M (1 ≤ M ≤ 4,500) $ two-cow rounds, determine the number of cows whose ranks can be precisely determined from the results. It is guaranteed that the results of the rounds will not be contradictory.

FJ的 N N N 1 ≤ N ≤ 100 1 \leq N \leq 100 1N100)头奶牛们最近参加了场程序设计竞赛。在赛场上,奶牛们按 1 , 2 , ⋯   , N 1, 2, \cdots, N 1,2,,N 依次编号。每头奶牛的编程能力不尽相同,并且没有哪两头奶牛的水平不相上下,也就是说,奶牛们的编程能力有明确的排名。 整个比赛被分成了若干轮,每一轮是两头指定编号的奶牛的对决。如果编号为 A A A 的奶牛的编程能力强于编号为 B B B 的奶牛 ( 1 ≤ A , B ≤ N 1 \leq A, B \leq N 1A,BN A ≠ B A \neq B A=B),那么她们的对决中,编号为 A A A 的奶牛总是能胜出。 FJ 想知道奶牛们编程能力的具体排名,于是他找来了奶牛们所有 M M M 1 ≤ M ≤ 4 , 500 1 \leq M \leq 4,500 1M4,500)轮比赛的结果,希望你能根据这些信息,推断出尽可能多的奶牛的编程能力排名。比赛结果保证不会自相矛盾。

输入格式

第一行两个用空格隔开的整数 N , M N, M N,M

2 ∼ M + 1 2\sim M + 1 2M+1 行,每行为两个用空格隔开的整数 A , B A, B A,B ,描述了参加某一轮比赛的奶牛的编号,以及结果(每行的第一个数的奶牛为胜者)。

输出格式

输出一行一个整数,表示排名可以确定的奶牛的数目。

输入输出样例 #1

输入 #1

5 5
4 3
4 2
3 2
1 2
2 5

输出 #1

2

说明/提示

样例解释:

编号为 2 2 2 的奶牛输给了编号为 1 , 3 , 4 1, 3, 4 1,3,4 的奶牛,也就是说她的水平比这 3 3 3 头奶牛都差。而编号为 5 5 5 的奶牛又输在了她的手下,也就是说,她的水平比编号为 5 5 5 的奶牛强一些。于是,编号为 2 2 2 的奶牛的排名必然为第 4 4 4,编号为 5 5 5 的奶牛的水平必然最差。其他 3 3 3 头奶牛的排名仍无法确定。

思路:

如果要确定排名那得与其余的节点有联系才能确定排名,也就是说这一个点得与其余的点连通才能有联系。
说到连通第一反应应该是并查集,还有Floyd求闭包问题
Floyd的前身应该是DFS,可通过暴力DFS的方法求解
1.统计能战胜i的奶牛数量
2.统计i能战胜的奶牛数量
3.如果满足两者之和为除自己以外的所有点的数量,就说明该点与其余点有联系

DFS:

#include <iostream>
#include <climits>
#include <limits>
#include <vector>

typedef long long ll;
typedef unsigned long long ull;
typedef long double ld;
typedef std::pair<int, int> PII;

#define rep(i, n) for(int i = 0; i < n; i++)
#define Rep(i, len, n) for(int i = len; i < n; i++)

const int INF = std::numeric_limits<int>::max();

int N, M;
std::vector<std::vector<int>> e;
std::vector<bool> vis;

void DFS(int u) {
	vis[u] = true;
	for(const int& v : e[u]) if(!vis[v]) DFS(v);	
}


int main() {
	std::ios::sync_with_stdio(false);
	std::cin.tie(nullptr), std::cout.tie(nullptr);
	
	std::cin >> N >> M;
	e.resize(N + 1);
	
	rep(i, M) {
		int a, b;
		std::cin >> a >> b;
		e[a].push_back(b);
	}
	
	int ans = 0;
	Rep(i, 1, N + 1) {
		int ij = 0;
		// 统计能战胜i的奶牛数量
		Rep(j, 1, N + 1) {
			if(i == j) continue;
			vis.assign(N + 1, false);
			DFS(j);	
			if(vis[i]) ij++; // 这个 i 是否被战胜
		}
		
		// 统计i能战胜的奶牛数量
		vis.assign(N + 1, false);
		int ji = 0;
		DFS(i);
		Rep(j, 1, N + 1) if(i != j && vis[j]) ji++;
		if(ji + ij == N - 1) ans++;
	}	
	
	std::cout << ans << '\n';
	
	
	
	
	return 0;
}

发现DFS复杂度太高,就想用dp优化成Floyd(闭包问题):
如果说 我从 i 到 j 是连通的那便不需要进行操作
如果说 我从 i 到 j 是不连通的,那遍找一个中转点 k 看看是否 i -> k 和 k -> j 都连通,也就是 i -> k -> j。

Floyd:

#include <iostream>
#include <climits>
#include <limits>
#include <vector>

typedef long long ll;
typedef unsigned long long ull;
typedef long double ld;
typedef std::pair<int, int> PII;

#define rep(i, n) for(int i = 0; i < n; i++)
#define Rep(i, len, n) for(int i = len; i < n; i++)

const int INF = std::numeric_limits<int>::max();

int N, M, maxn = 0;
std::vector<std::vector<int>> f;

inline void Floyd() {
	Rep(k, 1, N + 1) {
		Rep(i, 1, N + 1) {
			Rep(j, 1, N + 1) {
				f[i][j] |= f[i][k] & f[k][j];
			}
		}
	}
}

int main() {
	std::ios::sync_with_stdio(false);
	std::cin.tie(nullptr), std::cout.tie(nullptr);
	
	std::cin >> N >> M;
	f.resize(N + 1, std::vector<int>(N + 1, 0));
	Rep(i, 1, N + 1) f[i][i] = 0;
	rep(i, M) {
		int a, b;
		std::cin >> a >> b;
		f[a][b] = 1;
	}
	
	Floyd();
	int ans = 0;
	Rep(i, 1, N + 1) {
		int flag = 1;
		// i -> j || j -> i 有一个成立则为真,如果存在该条件为假的,那么flag为假
		Rep(j, 1, N + 1) if(i != j) flag &= (f[i][j] | f[j][i]); 
		ans += flag;
	}
	std::cout << ans << '\n';

	
	return 0;
}

但是Floyd标准方法求闭包问题复杂度还是到 O ( n 3 ) O(n^3) O(n3)还是不尽人意,在想又没有更优的方式进行计算。我们发现 如果 i 能到 j ,那么 j 能到的点 i 都能到,所以连通性就是与 j 连通的点都与 i 连通

b i t s e t bitset bitset 进行存储,bitset每位只占1个字节,大大减少了空间复杂度,bitset是存储的二进制,直接把 b i t s e t ( i ) bitset(i) bitset(i) b i t s e t ( j ) bitset(j) bitset(j) 异或于,更新 i 能通过的点。

外层循环遍历 j j j j j j 作为中转点,用 j j j 能到达的点去更新起点 i i i 能到达的点

为什么外层循环是 j j j 不是 i i i

因为 j j j 作为中转点的话因为是终点,会不断更新起点 i i i 的值, j j j 不断变大,每次用的都是更新后的 i i i 的值。

如果说外层循环是 i i i 的话, j j j 每次增加作为中转点的话, j > i j > i j>i 的部分都是用的老值,不会及时更新。

闭包问题(Floyd + bitset优化):

#include <iostream>
#include <climits>
#include <limits>
#include <bitset>
#include <vector>

typedef long long ll;
typedef unsigned long long ull;
typedef long double ld;
typedef std::pair<int, int> PII;

#define rep(i, n) for(int i = 0; i < n; i++)
#define Rep(i, len, n) for(int i = len; i < n; i++)

const int INF = std::numeric_limits<int>::max();
const int MAXN = 110;

int N, M;
std::vector<std::bitset<MAXN>> cow;

inline void Floyd() {
	Rep(j, 1, N + 1) {
		Rep(i, 1, N + 1) {
			if (cow[i][j]) cow[i] |= cow[j]; // i -> j j能到的点i全都能到
		}
	}
}

int main() {
	std::ios::sync_with_stdio(false);
	std::cin.tie(nullptr), std::cout.tie(nullptr);
	
	std::cin >> N >> M;
	cow.resize(N + 1);
	
	rep(i, M) {
		int a, b;
		std::cin >> a >> b;
		cow[a][b] = 1;
	}
	
	Floyd();
	
	int ans = 0;
	Rep(i, 1, N + 1) {
		int flag = 1;
		Rep(j, 1, N + 1) if(i != j) flag &= (cow[i][j] | cow[j][i]);
		ans += flag;
	}
	
	std::cout << ans << '\n';
	
	return 0;
}

并查集+拓扑排序:

这个思路拓扑排序起到了什么作用?

判断图中是否存在环:如果一个有向图能够成功进行拓扑排序,那么该图是有向无环图(DAG);如果在拓扑排序过程中发现存在入度始终不为 0 的节点,即无法完成拓扑排序,则说明图中存在环。在代码中,通过检查拓扑排序完成后所有节点的入度是否都为 0 来判断图是否有环

确定节点之间的胜负关系:在拓扑排序的过程中,通过遍历每个节点及其出边,可以确定节点之间的直接胜负关系。例如,当从队列中取出节点u,并遍历其邻接节点v时,将vis[u][v]设置为true,表示u战胜了v。同时,还会更新与u有胜负关系的其他节点与v的胜负关系,即如果k能战胜u,那么k也能战胜v。这样,通过拓扑排序可以完整地构建出整个图中节点之间的胜负关系矩阵vis,为后续确定每个节点是否能确定排名提供依据。

AC code(并查集 + 拓扑):

#include <iostream>
#include <climits>
#include <limits>
#include <vector>
#include <queue>

typedef long long ll;
typedef unsigned long long ull;
typedef long double ld;
typedef std::pair<int, int> PII;

#define rep(i, n) for(int i = 0; i < n; i++)
#define Rep(i, len, n) for(int i = len; i < n; i++)

const int INF = std::numeric_limits<int>::max();

int N, M;
std::vector<int> fa, sz, d;
std::vector<std::vector<int>> e;
std::vector<std::vector<bool>> vis;
std::queue<int> q;

inline void Init() {
	Rep(i, 1, N + 1) fa[i] = i, sz[i] = 1;
}

int Findest(int x) {
	return x == fa[x] ? x : fa[x] = Findest(fa[x]);
}

inline void Union(int x, int y) {
	int fx = Findest(x), fy = Findest(y);
	if(fx == fy) return ;
	if(sz[fx] > sz[fy]) std::swap(fx, fy);
	fa[fx] = fy;
	sz[fy] += sz[fx];
}

bool TopoSort() {
	Rep(i, 1, N + 1) if(!d[i])	q.push(i);
	
	while(!q.empty()) {
		int u = q.front();
		q.pop();
		
		for(const int& v : e[u]) { // u win  v lose
			vis[u][v] = true;
			Rep(k, 1, N + 1) if(vis[k][u]) vis[k][v] = true; // 如果 k赢u 那么k也能赢v
			if(--d[v] == 0) q.push(v);
		}
	}
	// 是否成环
	Rep(i, 1, N + 1) if(d[i] != 0) return false;
	return true;
}

int main() {
	std::ios::sync_with_stdio(false);
	std::cin.tie(nullptr), std::cout.tie(nullptr);
	
	std::cin >> N >> M;
	fa.resize(N + 1);
	sz.resize(N + 1);
	e.resize(N + 1);
	vis.resize(N + 1, std::vector<bool> (N + 1, false));
	d.resize(N + 1, 0);
	
	Init();
	rep(i, M) {
		int a, b;
		std::cin >> a >> b;
		e[a].push_back(b);
		d[b]++;
		Union(a, b);
	}	
	
	if(!TopoSort()) {
		std::cout << 0 << '\n';
		return 0;
	}
	
	int ans = 0;
	Rep(i, 1, N + 1) {
		int cnt = 0;
		int fx = Findest(i);
		Rep(j, 1, N + 1) {
			if(i == j) continue;
			int fy = Findest(j);
			if(fx == fy && (vis[i][j] || vis[j][i])) cnt++; // 在一个集合说明i与j有关系
		}
		if(cnt == sz[fx] - 1) ans++; // 如果这个点跟剩下的点都有关系的话就说明能确定排名
	}
	
	std::cout << ans << '\n';
	
	return 0;
}
/*

*/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值