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陣列,導致演算法有問題
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是由於邊比較多設定的,其他的也行
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);
}
}