天梯赛L3-015 球队“食物链”(状态压缩、记忆化搜索)

本文介绍了一种利用记忆化搜索解决足球联赛中寻找食物链问题的方法,通过优化搜索策略,避免了O(n!)的时间复杂度,适用于复杂比赛结果的数据。关键在于将比赛状态压缩并利用剪枝技巧提高效率。

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

链接

题目描述:

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

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

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

输入格式:

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

输出格式:

按题目要求找到“食物链” T 1 ​ , T 2 , ⋯ , T N T_1​,T_2, ⋯, T_N T1,T2,,TN ,将这 N N N 个数依次输出在一行上,数字间以 1 1 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

思路:

直接使用 d f s dfs dfs 复杂度为 O( n ! n! n!),会有一个测试点超时。我用了记忆化的方法,将 “当前搜索位置相同、且剩余未搜索的结点相同” 的状态看做是等价的。

例如:有 10 10 10 支球队,如果已访问路线为 1 , 2 , 3 , 4 1,2,3,4 1,2,3,4 并且这条路线往下搜没有可行解。那么就认为当已访问路线为 1 , 3 , 2 , 4 1,3,2,4 1,3,2,4 时,继续往下搜也不可能有可行解。

这样,等价的状态只搜索一次。

把已访问的路径状态压缩为 20 20 20 位二进制;当前位置最大取值 20 20 20,用 5 5 5 位二进制来表示。然后进行记忆化搜索。最坏情况就是所有的状态都搜索过一次,复杂度 O( n 2 n n2^n n2n)。

有很多题解用了剪枝来做这道题,剪枝条件为 “当剩余队伍中不存在战胜第一支队伍,那么这条线就没必要继续深入”。 显然这样的方法复杂度没有保证,有很多数据可以卡掉这样的程序:

20
-WWWWWWWWWWWWWWWWWWW
W-WWWWWWWWWWWWWWWWWD
WW-WWWWWWWWWWWWWWWWD
WWW-WWWWWWWWWWWWWWWD
WWWW-WWWWWWWWWWWWWWD
WWWWW-WWWWWWWWWWWWWD
WWWWWW-WWWWWWWWWWWWD
WWWWWWW-WWWWWWWWWWWD
WWWWWWWW-WWWWWWWWWWD
WWWWWWWWW-WWWWWWWWWD
WWWWWWWWWW-WWWWWWWWD
WWWWWWWWWWW-WWWWWWWD
WWWWWWWWWWWW-WWWWWWD
WWWWWWWWWWWWW-WWWWWD
WWWWWWWWWWWWWW-WWWWD
WWWWWWWWWWWWWWW-WWWD
WWWWWWWWWWWWWWWW-WWD
WWWWWWWWWWWWWWWWW-WD
WWWWWWWWWWWWWWWWWW-D
DWDDDDDDDDDDDDDDDDD-

这组数据的输出为:

1 20 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19

上面的剪枝方法不能很好地处理这样的数据。

#include<bits/stdc++.h>
using namespace std;
const int N=30;
char mp[N][N];
int n,path[N],vis;
unordered_set<int> st;

bool dfs(int x,int step){
	vis^=(1<<x);
	if(st.count(vis|(x<<21))){ vis^=(1<<x); return false; }
	st.insert(vis|(x<<21));
	path[step]=x;
	if(step==n&&(mp[x][1]=='W'||mp[1][x]=='L')) return true;
	for(int i=1;i<=n;i++){
		if(!(vis&(1<<i))&&(mp[x][i]=='W'||mp[i][x]=='L')){
			if(dfs(i,step+1)) return true;
		}
	}
	vis^=(1<<x);
	return false;
}

int main(){
	(cin>>n).get();
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++)
			cin>>mp[i][j];
		if(i!=n) cin.get();
	}
	bool flag=dfs(1,1);
	if(flag) for(int i=1;i<=n;i++) cout<<path[i]<<" \n"[i==n];
	else cout<<"No Solution\n";
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

m0_51864047

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值