【SGU448】Controlled Tournament(状态压缩动态规划)

题目链接

【SGU448】Controlled Tournament


题目大意

给定比赛人员个数 n ,你希望赢的人的编号 m 以及一张 n × n 的胜负表,第 i 行第 j 列为 1 ,代表 i 能赢 j
赛制为淘汰赛,求 m 最后能赢,且总比赛树的最小高度时,一共有多少种可能方案。
n 16


题解

看到 n 16 ,果断使用状态压缩动态规划。
d p [ i ] [ h e i g h t ] [ m a s k ] 表示已比赛完的人的状态为 m a s k ,其中第 i 个人胜利了,且比赛树的高度为 h e i g h t
状态转移:枚举子集,当子集 s u b m a s k 中包含 i i 能胜过补集中的 j 的时候,可以转移。
即: d p [ i ] [ h e i g h t ] [ m a s k ] = d p [ i ] [ h e i g h t 1 ] [ s u b m a s k ] × d p [ j ] [ h e i g h t 1 ] [ m a s k s u b m a s k ]
具体细节见代码。


代码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;
}

复杂度分析

状态数量: O ( n × log n × 2 n )
众所周知,子集枚举是 O ( 3 n ) 的。
所以总复杂度是: O ( n × log n × 3 n )
但是为何没有超时呢?
因为有很多状态是没用的。


总结

有没用状态的动态规划往往可以使用记忆化搜索的手段,从而将程序的效率进一步提高。

猜你喜欢

转载自blog.csdn.net/weixin_42068627/article/details/80368749