题目链接
题目大意
给定比赛人员个数
,你希望赢的人的编号
以及一张
的胜负表,第
行第
列为
,代表
能赢
。
赛制为淘汰赛,求
最后能赢,且总比赛树的最小高度时,一共有多少种可能方案。
。
题解
看到
,果断使用状态压缩动态规划。
表示已比赛完的人的状态为
,其中第
个人胜利了,且比赛树的高度为
。
状态转移:枚举子集,当子集
中包含
,
能胜过补集中的
的时候,可以转移。
即:
。
具体细节见代码。
代码no.1
#include <cstdio>
#include <cstring>
#define mxn 16
#define lgn 4
#define rep(i,l,r) for(int i=(l);i<(r);i++)
typedef long long ll;
int n,m,hi,a[mxn][mxn],cnt[1<<mxn];
int dp[mxn][lgn|1][1<<mxn];
int search(int i,int k,int msk) {
if(~dp[i][k][msk]) return dp[i][k][msk];
if(msk==1<<i) return dp[i][k][msk]=1;
if(1<<k<cnt[msk]) return dp[i][k][msk]=0;
dp[i][k][msk]=0;
for(int msk0=msk&(msk-1);msk0;msk0=msk&(msk0-1))
if(msk0&(1<<i)) {
int msk1=msk^msk0;
rep(j,0,n) if(a[i][j]&&(msk1&(1<<j)))
dp[i][k][msk]+=search(i,k-1,msk0)*search(j,k-1,msk1);
}
return dp[i][k][msk];
}
int main() {
memset(dp,-1,sizeof(dp));
scanf("%d%d",&n,&m),m--;
rep(i,1,1<<n)
cnt[i]=cnt[i>>1]+(i&1);
rep(i,0,n) rep(j,0,n)
scanf("%1d",a[i]+j);
for(;1<<hi<n;hi++);
search(m,hi,(1<<n)-1);
if(dp[m][hi][(1<<n)-1]==-1)
dp[m][hi][(1<<n)-1]=0;
printf("%d\n",dp[m][hi][(1<<n)-1]);
return 0;
}
代码no.2(加了一些小优化,然并卵)
#include <cstdio>
#include <cstring>
#define mxn 16
#define lgn 4
#define rep(i,l,r) for(int i=(l);i<(r);i++)
typedef long long ll;
int n,m,hi,a[mxn][mxn],cnt[1<<mxn];
int dp[mxn][lgn|1][1<<mxn];
int search(int i,int k,int msk) {
if(~dp[i][k][msk]) return dp[i][k][msk];
if(msk==1<<i) return dp[i][k][msk]=1;
if(1<<k<cnt[msk]) return dp[i][k][msk]=0;
if(!(msk&(1<<i))) return dp[i][k][msk]=0;
dp[i][k][msk]=0;
int msk0,msk1,tmsk=msk^(1<<i);
for(int tmsk0=tmsk&(tmsk-1);tmsk0;tmsk0=tmsk&(tmsk0-1)) {
msk0=tmsk0|(1<<i);
msk1=msk^msk0;
rep(j,0,n) if(a[i][j]&&(msk1&(1<<j)))
dp[i][k][msk]+=search(i,k-1,msk0)*search(j,k-1,msk1);
}
msk0=1<<i;
msk1=msk^msk0;
rep(j,0,n) if(a[i][j]&&(msk1&(1<<j)))
dp[i][k][msk]+=search(i,k-1,msk0)*search(j,k-1,msk1);
return dp[i][k][msk];
}
int main() {
memset(dp,-1,sizeof(dp));
scanf("%d%d",&n,&m),m--;
rep(i,1,1<<n)
cnt[i]=cnt[i>>1]+(i&1);
rep(i,0,n) rep(j,0,n)
scanf("%1d",a[i]+j);
for(;1<<hi<n;hi++);
search(m,hi,(1<<n)-1);
if(dp[m][hi][(1<<n)-1]==-1)
dp[m][hi][(1<<n)-1]=0;
printf("%d\n",dp[m][hi][(1<<n)-1]);
return 0;
}
复杂度分析
状态数量:
。
众所周知,子集枚举是
的。
所以总复杂度是:
。
但是为何没有超时呢?
因为有很多状态是没用的。
总结
有没用状态的动态规划往往可以使用记忆化搜索的手段,从而将程序的效率进一步提高。