ZOJ 3738 —— Buy the Pets(状态压缩DP)

题目请戳这里

题意是有N个人要买宠物,有M只猫和P只狗,人的编号是1~N,猫的编号是N+1~N+M,狗的编号是N+M+1~N+M+P;每个人都买一只猫和一只狗;

然后有Q条限制,表明对应编号的人和猫(过敏)或是猫和狗(打架)不能在一起,然后问有多少种购买方案使得N个人都满足,所谓的满足就是这个人买的猫和狗不打架,同时人不会对买的猫造成过敏。

因为N,M,P都不超过10,所以用状态压缩DP统计。

首先统计出人和猫的所有搭配方案,这个问题可以看成是一个N*M的棋盘,其中某些点(人对猫过敏)不能放置,问放置N个不互相攻击的车的方案数。

这个问题就比较好解决了,用一个整形表示猫的集合,然后按照人的编号进行dp,判断当前人和当前猫是否能在一起,能就把状态转移过去。

状态转移方程:dp[i][k|(1<<j)] += dp[i-1][k],其中k代表已经被取走的猫的集合,j是新加进来的猫。

这样就可以求出所有人和猫的组合方案。

找出dp[n][i]中,二进制刚好有N个1的i,因为这就是对应的可行的人和猫的方案,同时也对应着这个方案里面猫的编号,根据这个方案再去求出猫和狗的组合方案,方法跟上面类似,两个方案数乘起来就是当前猫集合的总方案数。

最后把所有符合条件的方案数累加起来就是答案了。

#include<cstdio>
#include<cstring>
#define LL long long
inline void getnum(int& x){
    x=0;
    char c=getchar();
    while(c<48 || c>57)    c=getchar();
    while(c>=48 && c<=57){
        x = x*10+c-48;
        c=getchar();
    }
}
int n, m, p, q, i, j, k, cnt, l[11];
bool f[1001][1001];
LL dp1[11][1<<10], dp2[11][1<<10], ans;
int main(){
    while(~scanf("%d %d %d", &n, &m, &p)){
        getnum(q);
        memset(f, 0, sizeof(f));
        while(q--){
            getnum(i); getnum(j);
            f[i][j]=1;
            f[j][i]=1;
        }
        if(n>m || n>p){
            puts("0");
            continue;
        }
        memset(dp1, 0, sizeof(dp1));
        //这里dp1记录人和猫的组合数
        dp1[0][0]=1;
        for(i=1; i<=n; i++){
            for(j=0; j<(1<<m); j++){
                for(k=0; k<m; k++){//遍历猫,猫的编号比实际编号少N+1
                    if(!f[i][k+n+1] && !(j&(1<<k))){
                        dp1[i][j|(1<<k)] += dp1[i-1][j];
                    }
                }
            }
        }
        ans=0;
        for(i=0; i<(1<<m); i++){
            cnt=0;
            //先判断该组合是否合法,即是否恰好N只猫
            for(j=0; j<m; j++){
                if(i&(1<<j)){
                    l[++cnt]=j;//存储猫的编号(这里猫的编号比实际编号少N+1)
                }
            }
            if(cnt!=n)    continue;
            memset(dp2, 0, sizeof(dp2));
            dp2[0][0]=1;
            //统计猫和狗的组合数
            for(j=1; j<=n; j++){
                for(k=0; k<(1<<p); k++){
                    for(int x=0; x<p; x++){//遍历狗,狗的编号比实际编号少N+M+1
                        if(!f[l[j]+n+1][x+n+m+1] && !(k&(1<<x))){
                            dp2[j][k|(1<<x)]+=dp2[j-1][k];
                        }
                    }
                }
            }
            LL tmp=0;
            for(j=1; j<(1<<p); j++){
                cnt=0;
                for(k=0; k<p; k++){
                    if(j&(1<<k))    cnt++;
                }
                //找出那些狗恰好N只的集合
                if(cnt==n)    tmp+=dp2[n][j];
            }
            ans += tmp*dp1[n][i];
        }
        printf("%lld\n", ans);
    }
    return 0;
}


  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值