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

本文介绍了一道经典的算法竞赛题目,通过状态压缩动态规划的方法求解特定条件下比赛获胜方案的数量。利用记忆化搜索减少重复计算,提高了算法效率。

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

题目链接

【SGU 448】Controlled Tournament


题目大意

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


题解

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


代码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×logn×2n) O ( n × log ⁡ n × 2 n )
众所周知,子集枚举是 O(3n) O ( 3 n ) 的。
所以总复杂度是: O(n×logn×3n) O ( n × log ⁡ n × 3 n )
但是为何没有超时呢?
因为有很多状态是没用的。


总结

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值