1. 程式人生 > 其它 >2017年天梯賽大區賽題集 7-15 球隊“食物鏈” (30 分)(DFS,剪枝)

2017年天梯賽大區賽題集 7-15 球隊“食物鏈” (30 分)(DFS,剪枝)

技術標籤:2017年天梯賽大區賽題集dfs剪枝

某國的足球聯賽中有N支參賽球隊,編號從1至N。聯賽採用主客場雙迴圈賽制,參賽球隊兩兩之間在雙方主場各賽一場。

聯賽戰罷,結果已經塵埃落定。此時,聯賽主席突發奇想,希望從中找出一條包含所有球隊的“食物鏈”,來說明聯賽的精彩程度。“食物鏈”為一個1至N的排列{T​1​​T​2​​⋯T​N​​},滿足:球隊T​1​​戰勝過球隊T​2​​,球隊T​2​​戰勝過球隊T​3​​,⋯,球隊T​(N−1)​​戰勝過球隊T​N​​,球隊T​N​​戰勝過球隊T​1​​。

現在主席請你從聯賽結果中找出“食物鏈”。若存在多條“食物鏈”,請找出字典序最小的。

注:排列{a​1​​a​2​​⋯a​N​​}在字典序上小於排列{b​1​​b​2​​⋯b​N​​},當且僅當存在整數K(1≤K≤N),滿足:a​K​​<b​K​​且對於任意小於K的正整數i,a​i​​=b​i​​。

輸入格式:

輸入第一行給出一個整數N(2≤N≤20),為參賽球隊數。隨後N行,每行N個字元,給出了N×N的聯賽結果表,其中第i行第j列的字元為球隊i在主場對陣球隊j的比賽結果:W表示球隊i戰勝球隊j,L表示球隊i負於球隊j,D表示兩隊打平,-表示無效(當i=j時)。輸入中無多餘空格。

輸出格式:

按題目要求找到“食物鏈”T​1​​T​2​​⋯T​N​​,將這N個數依次輸出在一行上,數字間以1個空格分隔,行的首尾不得有多餘空格。若不存在“食物鏈”,輸出“No Solution”。

輸入樣例1:

5
-LWDW
W-LDW
WW-LW
DWW-W
DDLW-

輸出樣例1:

1 3 5 4 2

輸入樣例2:

5
-WDDW
D-DWL
DD-DW
DDW-D
DDDD-

輸出樣例2:

No Solution

題意:

給定一個N*N的球隊勝負表,求出一個球隊"食物鏈"環,其中連線的條件為前一個球隊戰勝過後一個球隊,如果存在這個環則輸出字典序最小的那個,不存在輸出無解

思路:

對於勝負表的處理,我們可以開一個二維的布林矩陣G來處理,如果W則讓G[i][j] = true,如果為L則讓G[j][i] = true,這樣就可以在O(1)時間內判斷球隊i是否戰勝國球隊j(注意:並不是G[i][j] =!G[j][i] 因為可能題目中提到“聯賽採用主客場雙迴圈賽制,參賽球隊兩兩之間在雙方主場各賽一場”,所以可能i在j的客場戰勝了,同時j在i的客場也戰勝了,所以可能存在G[i][j],G[j][i]同時為正或同時為負的情況)

再說對字典序的處理,我們每次由小到大按順序搜尋得到的第一個答案就是字典序就是最小的。但同時注意到,這裡如果答案存在的話,是以一個環的形式存在的,那既然是環的話,我們就可以將環旋轉,這樣一定能夠保證第一個球隊編號是1。(比如說找到了順序是2 3145 那麼我們可以將這個“環”向左移動兩格或者向右移動三格得到 1 4 5 2 3)所以我們直接從1開始搜尋就行了

經過上述的處理,應該是可以順利通過五個點得到22分,測試點4顯示超時。這是因為我們做了大量無用的搜尋,最壞情況是我們一直搜尋到最後一個球隊,才發現這個球隊沒有戰勝球隊1,這樣就逐步返回到上一層繼續搜尋。其實我們可以在每次搜尋之前就遍歷一遍,當前是否還存在戰勝過球隊1的球隊,如果不存在的話,這種情況一定是不合法的,就不用再往下進行搜尋了,直接return就行了。

程式碼:

//7-15 球隊“食物鏈” (30 分)
#include<vector>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 25;

int n;
char ch;
bool G[N][N] , vis[N];
vector<int> a , ans;

bool judge()							//判斷當前是否存在一個未訪問的球隊 並且戰勝過球隊1 
{
    for(int i = 1 ; i <= n ; i++)
    {
        if(!vis[i] && G[i][1])
            return true;
    }
    return false;
}

void dfs(int u , vector<int> path)
{
	if(path.size() == n - 1)			//搜到了最後一個球隊 對ans進行賦值 並return 
	{
		ans = path;
		return ;
	}
	
	if(ans.size())						//已經搜尋到了答案 沒必要再搜尋了 
		return ;
	
	if(!judge())						//判斷當前是否還有球隊戰勝過球隊1 沒有則return 
	    return ;
	
	for(int i = 1 ; i <= n ; i++)		//按照從小到大順序搜尋 
	{
		if(!vis[i] && G[u][i])			//如果該球隊未訪問過 並且 該球隊戰勝過當前球隊 
		{
			vis[i] = true;				//置為訪問過 
			path.push_back(i);			//存入路徑陣列 
			dfs(i , path);				//繼續下一個球隊的搜尋 
			path.pop_back();			//恢復現場 
			vis[i] = false;
		}
	}
}

int main()
{
 	cin>>n;
 	for(int i = 1 ; i <= n ; i++)
 	{
		for(int j = 1 ; j <= n ; j++)
		{
			cin>>ch;
			if(ch == 'W')				//如果是 W 則  G[i][j] = true
				G[i][j] = true;
			else if(ch == 'L')			//如果是 L 則  G[j][i] = true
				G[j][i] = true; 
		} 
	}
	
	vis[1] = true;						//把起點置為訪問過 
	dfs(1 , a);							//傳入當前點 和 路徑陣列 
	if(ans.size())						//如果答案存在 
	{
	    cout<<1;						//先輸出起點 
		for(int i = 0 ; i < n - 1 ; i++)
			cout<<" "<<ans[i];
	}
	else								//無解 
		cout<<"No Solution"<<endl;
	return 0;
}