7-11 球队“食物链” (30 分)

这篇博客讨论了一种求解足球联赛中字典序最小的‘食物链’的方法,即通过深度优先搜索(DFS)算法,并利用剪枝策略避免超时。输入为联赛结果表格,输出为满足特定条件的球队排列。当无法找到符合条件的‘食物链’时,输出NoSolution。算法关键在于在DFS过程中检查是否存在回路,并在适当时候终止搜索。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

某国的足球联赛中有N支参赛球队,编号从1至N。联赛采用主客场双循环赛制,参赛球队两两之间在双方主场各赛一场。

联赛战罢,结果已经尘埃落定。此时,联赛主席突发奇想,希望从中找出一条包含所有球队的“食物链”,来说明联赛的精彩程度。“食物链”为一个1至N的排列{ T1​ T2​ ⋯ TN​ },满足:球队T1​战胜过球队T2​,球队T2​战胜过球队T3​,⋯,球队T(N−1)​战胜过球队TN​,球队TN​战胜过球队T1​。

现在主席请你从联赛结果中找出“食物链”。若存在多条“食物链”,请找出字典序最小的。

注:排列{ a1​ a2​ ⋯ aN​}在字典序上小于排列{ b1​ b2​ ⋯ bN​ },当且仅当存在整数K(1≤K≤N),满足:aK​<bK​且对于任意小于K的正整数i,ai​=bi​。

输入格式:

输入第一行给出一个整数N(2≤N≤20),为参赛球队数。随后N行,每行N个字符,给出了N×N的联赛结果表,其中第i行第j列的字符为球队i在主场对阵球队j的比赛结果:W表示球队i战胜球队j,L表示球队i负于球队j,D表示两队打平,-表示无效(当i=j时)。输入中无多余空格。

输出格式:

按题目要求找到“食物链”T1​ T2​ ⋯ TN​,将这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

这是一个求欧拉迹的问题,采用深度优先算法,但是需要剪枝进行优化以防超时。

解释一下dfs函数的功能:

此处先以参数为线索作一个说明,再具体讲解函数的思路。

p是一个二维数组,记录经过处理的原始数据,直接读入,不必也不能修改。

sg是一个得解的信号量,若sg置为1则所有后续遍历不再进行。(第一处剪枝)

fs是起点标识,遍历结束后,用来检验是否回到起点,实际上对任何输入,只需考虑从结点1出发有无解。(若1无解而后面某个结点出发有解,由于解是一个回路,则该解也可以看作从1出发,从而矛盾。另外,从1出发字典序永远保证最小)因而这个变量是可有可无的。但为了不失一般性、提高可读性,此处不选择将其省略。

cur是当前访问结点。

l当前路径包含结点数,n是结点数,当两者相等时,检验是否形成了回路。

path是路径数组兼具访问数组的作用。该数组除了对1号结点的记录只有访问数组的作用之外,任何非0值都记录能够到达该数组的上一个结点。

st是一个对栈的引用,当得到可行解时,将回溯path数组逆序写入路径,最后出栈打印即可。

函数第一行,是对得解之后后续搜索的阻断。

接下来是一个检验,如果当前路径所含结点等于总结点,进一步考虑是否成环,只需要看当前结点(即最后一个结点)有无到初始结点的路径即可,如果没有则返回,因为这条路已经走完。当成环时,回溯path数组,将元素逐一入栈,最后将起点入栈。将得解信号置为1并返回。

以上为路径探索终止时所进行的操作,函数剩余部分是对一般情况的探索:

首先是,每次循环开始时应该检测剩余未访问结点中有无可以回到起点的结点,如果没有这样的结点就意味着没有必要继续深入了,这是第二处剪枝,也是最关键的剪枝。(此处采用最简单的遍历方式检验,也可以用集合实现)

接下来要查找这些结点中有无未被访问的可达结点,一旦发现这样的结点,将当前的path数组复制并作为参数传给下一级dfs函数。(path数组不能共享,共享会引起错误)

主函数就不作说明了。

#include <iostream>
#include <vector>
#include <stack>
using namespace std;
void dfs(int p[21][21],int *sg,int fs,int cur,int l,int n,int path[22],stack<int> &st){
	if(*sg)return;
	if(l==n){
		if(p[cur][fs]){
			int temp=cur;
			do{
				st.push(temp);
				temp=path[temp];
			}while(temp!=fs);
			st.push(fs);
			*sg=1;
			return;
		}
		else return;
	}
	for(int i=1,flg=0;i<=n;i++,flg=0){
		for(int j=1;j<=n;j++)if(p[j][fs]&&!path[j]){flg=1;break;}
		if(p[cur][i]&&flg&&!path[i]){
			int path0[21];
			for(int j=1;j<=n;j++)path0[j]=path[j];
			path0[i]=cur;
			dfs(p,sg,fs,i,l+1,n,path0,st);
		}
	}
}
int main(void){
	int n,sg=0,p[21][21]={0},path[21]={0};path[1]=1;
	cin>>n;
	stack<int> st;
	for(int i=1;i<=n;i++){
		string s;
		cin>>s;
		for(int j=0;j<s.size();j++){
			if(s[j]=='L')p[j+1][i]=1;
			if(s[j]=='W')p[i][j+1]=1;
		}
	}
	dfs(p,&sg,1,1,1,n,path,st);
	if(st.empty())cout<<"No Solution";
	else{
		while(!st.empty()){
			cout<<st.top();
			st.pop();
			if(!st.empty())cout<<' ';
		}
	}
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值