链接
题目描述:
某国的足球联赛中有 支参赛球队,编号从 至 。联赛采用主客场双循环赛制,参赛球队两两之间在双方主场各赛一场。
联赛战罢,结果已经尘埃落定。此时,联赛主席突发奇想,希望从中找出一条包含所有球队的“食物链”,来说明联赛的精彩程度。“食物链”为一个 至 的排列 {},满足:球队 战胜过球队 ,球队 战胜过球队 ,⋯,球队 战胜过球队 ,球队 战胜过球队 。
现在主席请你从联赛结果中找出“食物链”。若存在多条“食物链”,请找出字典序最小的。
输入格式:
输入第一行给出一个整数 (),为参赛球队数。随后 行,每行 个字符,给出了 的联赛结果表,其中第 行第 列的字符为球队 在主场对阵球队 的比赛结果: 表示球队 战胜球队 , 表示球队 负于球队 , 表示两队打平, 表示无效(当 时)。输入中无多余空格。
输出格式:
按题目要求找到“食物链” ,将这 个数依次输出在一行上,数字间以 个空格分隔,行的首尾不得有多余空格。若不存在“食物链”,输出 “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
思路:
直接使用 复杂度为 O()。会有一个测试点超时。我用了记忆化的方法,将 “当前搜索位置相同、且剩余未搜索的结点相同” 的状态看做是等价的。
例如:有 支球队,如果已访问路线为 并且这条路线往下搜没有可行解。那么就认为当已访问路线为 时,继续往下搜也不可能有可行解。
这样,等价的状态只搜索一次。
把已访问的路径状态压缩为 位二进制;当前位置最大取值 ,用 位二进制来表示。然后进行记忆化搜索。最坏情况就是所有的状态都搜索过一次,复杂度 O()。
有很多题解用了剪枝来做这道题,剪枝条件为 “当剩余队伍中不存在战胜第一支队伍,那么这条线就没必要继续深入”。 显然这样的方法复杂度没有保证,有很多数据可以卡掉这样的程序:
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";
}