Acwing4244.牛的比赛
题目部分
N 头奶牛,编号 1∼N,一起参加比赛。
奶牛的战斗力两两不同。
这些奶牛之间已经进行了 M轮两两对决。
在对决中,战斗力高的奶牛一定会战胜战斗力低的奶牛。
请问,通过上述 M轮对决的结果,可以确定多少头奶牛的具体战斗力排名。
输入格式
第一行包含两个整数 N ,M。
接下来 M 行,每行包含两个整数 a ,b ,表示奶牛 a 和奶牛 b 之间进行了对决,并且奶牛 a 战胜了奶牛 b。
输出格式
输出可以确定具体战斗力排名的奶牛数量。
数据范围
1≤N ≤100,
1≤M ≤4500,
数据保证合法。
输入样例:
5 5
4 3
4 2
3 2
1 2
2 5
输出样例:
2
样例解释
2号奶牛输给了 1,3,4 号奶牛,战胜了 5 号奶牛,可以确定它的战斗力排名为 4。
5号奶牛输给了排在第 4 的 2 号奶牛,所以它的战斗力排名为 5。
其它奶牛不确定。
解法
首先这道题,虽然大佬们说是板子题,但是对我图论新手来说,这题还是相当有意思的!学到了很多!
错误解法
首先原先的我,是有点懵的,在看到这道题时,我们能够通过floyd得出这道题?我是怎么也想不出
我只能分析出某头牛的排名能确定下来,是因为它间接和其余的牛battle过
然后看了下大佬的题解(仅文字部分):
这是link,才做出了错误解法
还原下,我做出错误解法的思路哈:
我们可以把这些两头牛有没有间接比较过,转换为每个牛都是对应图中的节点,然后两点中可达,则为间接比较过
由此我通过转换关系把题目图化了,但是我不是添加单条有向边,而是添加了双向边
赋值为1的,代表着战胜
赋值为0的,代表着战败
想得很美好,由于对floyd算法运行流程不熟悉,导致误以为只要判断通过战败可达其余点或者战胜可达其余点即可,可是在floyd过程中,却是我们可以先通过战败边到另一个点,然后再由这个点选择战胜边接着刷新dist数组,导致算法有问题
java
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
/**
* @author:天才玩家M
* @date:2023/11/29 16:05
* @description:TODO
*/
public class Main {
static int n,m;
static int N=110;
static int INF=Integer.MAX_VALUE/4;
static int [][]dist=new int[N][N];
public static void floyd(){
for (int k = 1; k <=n; k++) {
for (int i = 1; i <=n; i++) {
for (int j = 1; j <=n ; j++) {
dist[i][j]=Math.min(dist[i][j],dist[i][k]+dist[k][j]);
}
}
}
}
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String s = br.readLine();
String[] s1 = s.split(" ");
n = Integer.parseInt(s1[0]);
m = Integer.parseInt(s1[1]);
for (int i = 0; i <= n; i++) {
for (int j = 0; j <=n; j++) {
if(i==j){
dist[i][j]=1;
}else{
dist[i][j]=INF;
}
}
}
for (int i = 0; i < m; i++) {
s = br.readLine();s1 = s.split(" ");
int a=Integer.parseInt(s1[0]);
int b=Integer.parseInt(s1[1]);
dist[a][b]=1;//代表可达且胜利
//dist[b][a]=0;不能多出这一条,这只能是a->b单向的
//不对上面的说法是错的,我们可以设置可达不过要与胜利有所区别
dist[b][a]=0;//代表失败的可达,
//想法很好,可是你有没有想过我们失败的点可以沿着失败的路线上去到达某个成功的点然后由成功的点再蔓延下去呢?
//这种情况就违背了我们最初的意愿了,只让他们沿着成功之路或者失败之路而上,且我们只统计成功之路以及失败之路
//2:可是这种情况下,如果夹杂成成功与失败,那我们又不会去统计!!!
//那为什么正确答案为6而我们的为2呢?
//解决了,答案浮出水面了,就是因为不去统计,假如1已经确定是倒数第一,而你唯一赢他的你又不算,那这不就少了吗?
}
floyd();
int res=0;
for (int i = 1; i <=n; i++) {
int count1=0;
int count=0;
for (int j = 1; j <=n; j++) {
if(dist[i][j]==0){
count1++;
}
else if(dist[i][j]!=INF){
count++;
}
}
if(count==n||count1==n){
res++;
}
}
System.out.println(res);
}
}
正确做法
大佬优雅地解决我一直困扰的战败与战胜,通过仅赋值单向边,而之后同时判断if(dist[i][j]<=INF/2||dist[j][i]<=INF/2),来确定这个对手是否交战过,并且由于仅赋值了战胜那一条,目前点要么战胜要么战败,而且看得比我的透彻,其实做出错误做法时,我就已经被要么战胜要么战败给迷惑了,
可是实际情况下是只要战过,无论成败即知排名(挺好想的,只要交手过,赢你的在前,输你的在后,你一定可以找到个位置).
至于INF/2是由于边比较多设定的,其他的也行
java
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
/**
* @author:天才玩家M
* @date:2023/11/29 20:38
* @description:TODO
*/
public class Main1 {
static int n,m;
static int N=110;
static int INF=Integer.MAX_VALUE/2;
static int [][]dist=new int[N][N];
public static void floyd(){
for (int k = 1; k <=n; k++) {
for (int i = 1; i <=n; i++) {
for (int j = 1; j <=n ; j++) {
dist[i][j]=Math.min(dist[i][j],dist[i][k]+dist[k][j]);
}
}
}
}
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String s = br.readLine();
String[] s1 = s.split(" ");
n = Integer.parseInt(s1[0]);
m = Integer.parseInt(s1[1]);
for (int i = 0; i <= n; i++) {
for (int j = 0; j <=n; j++) {
if(i==j){
dist[i][j]=1;
}else{
dist[i][j]=INF;
}
}
}
for (int i = 0; i < m; i++) {
s = br.readLine();s1 = s.split(" ");
int a=Integer.parseInt(s1[0]);
int b=Integer.parseInt(s1[1]);
dist[a][b]=1;
}
floyd();
int res=0;
for (int i = 1; i <=n; i++) {
int count=0;
for (int j = 1; j <=n; j++) {
if(dist[i][j]<=INF/2||dist[j][i]<=INF/2){
count++;
}
}
if(count==n){
res++;
}
}
System.out.println(res);
}
}