题目链接:https://codeforces.com/gym/100837
题目大意:给出n个人之间的胜负关系,问能构造出多少种尽可能层数少的竞赛树使得m能够赢
题目思路:一个坑点在于正确理解层数少的含义,层数少就是说每一轮都没有人空闲都在比赛,那么很容易得出竞赛树的高度就等于ceil(log2(n)),参照二叉树的性质。然后由于这个很小的数据范围,很容易想到是通过状压PD获得最终结果,dp建三维,第一维是谁赢,第二维当前是第几层,第三维是当前参与比赛的人的集合。那么dp[u][h][s]就可以通过枚举s的各种情况获得,枚举得到i,然后要求u在i中,然后用s^i就能得到另一边的状态,观察另一边的状态中是否有人(k)能被打败,如果有的话,继续以k能被打败,h-1层,s^i的状态继续递归前进,最后得到最终答案。对于剪枝,一个是记忆化搜索 ,如果已经有结果了直接return,其次是如果当前状态只有一个人了,那么他一定是冠军,返回1,还有一个剪枝是1<<h表示当前层数最多的人数,如果状态表示的1的数量比1<<h要多,那么肯定不行直接return 0,这是一个很强的剪枝,也比较难想到
以下是代码:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define per(i,a,b) for(int i=a;i>=b;i--)
#define ll long long
const int MAXN = 20+5;
int a[MAXN][MAXN],n,m,h,dp[MAXN][MAXN][1<<16],bits[1<<16];
int dfs(int u,int h,int s){
if(dp[u][h][s]!=-1)return dp[u][h][s];
if((1<<h)<bits[s])return 0;
if(bits[s]==1)return 1;
dp[u][h][s]=0;
for(int i=s&(s-1);i;i=s&(i-1)){
if(i&(1<<u)){
int j=s^i;
rep(k,0,n-1){
if(a[u][k]&&((1<<k)&j)){
dp[u][h][s]+=dfs(u,h-1,i)*dfs(k,h-1,j);
}
}
}
}
return dp[u][h][s];
}
int main()
{
freopen("f.in","r",stdin);
freopen("f.out","w",stdout);
memset(bits,0,sizeof(bits));
rep(i,1,(1<<16)-1){
if(i&1)bits[i]=bits[i>>1]+1;
else bits[i]=bits[i>>1];
}
scanf("%d%d",&n,&m);
h=ceil(log2(n));
rep(i,0,n-1){
rep(j,0,n-1){
scanf("%d",&a[i][j]);
}
}
memset(dp,-1,sizeof(dp));
int ans=dfs(m-1,h,(1<<n)-1);
printf("%d\n",ans);
return 0;
}