蓝桥杯:国际象棋【状态压缩dp】

写了有五六道状态压缩dp的题,本菜发现这种题目其实和线性dp差不多,只不过是多了一个状态压缩,即将某个状态压缩到一串二进制中;

先看题:

 

因为行数很小,所以不难想到是状态压缩dp;

这道题和洛谷的炮兵阵地很像,都是一行的状态可以牵扯到上两个状态,所以我们在对i行进行状态转移方程的时候,我们得考虑第i-1和i-2行的状态,然后这道题目从某种意义上来说,比炮兵阵地要难一些,因为这个还有一个马的个数限制,所以我们得开4个维度的dp数组;

我们先明确一下,状态集合的定义,即dp数组的定义:

f[i][j][k][z]表示第i行的状态为j,i-1行的状态为k,且1~i行总共有z头马;

然后我们就可以想状态转移方程了,这个很简单,只要第i、i-1、i-2行互不干扰,那么我们就有:

【我们即第i行状态为j,i-1行状态为k,i-2行状态为z,且前1~i行总共有q头马,第i行的马有p头】

f[i][j][k][q]+=f[i-1][k][z][q-p]

这里记录一下自己踩的坑吧,本菜刚开始的时候是用了滚动数组优化的,但是这里本菜忘了一件事情:滚动数组在解决求方案数的dp中要初始化,即将之前用过的空间初始化,要不然会叠加以前计算过的状态;

但是最后本菜发现空间完全够用,所以就没用滚动数组优化了;

另外还有一个坑,那就是,如果我们发现i和i-1行冲突了,那么就不用枚举下去了,直接枚举i-1行的下一个状态,否则以下的循环会建立在i-1和i行冲突的基础之上,如果在状态转移的同一个循环里进行判断,那么不会影响结果,但会浪费大量的时间,甚至tle

那么接下来就是代码了:

#include<iostream>
#include<algorithm>
#define ll long long
using namespace std;

const int N=110,M=1<<6,mod=1e9+7;

struct p{
    int n=0;//棋子个数
}st[M+10];

ll f[N][M][M][21];//表示第i行状态为j,第i-1行状态为k的方案数,且有z头马的方案数
int n,m,p;
ll ans;

void init(){//初始化每一个状态
    
    for(int i=0;i<1<<n;i++){
        for(int j=0;j<n;j++)
            if(i>>j&1) st[i].n++;
    }
    
}

bool jud(int i,int j){
    
    return ((i>>2)&j)||((i<<2)&j)||((j<<2)&i)||((j>>2)&i);
}

bool jud1(int i,int j){
    
    return ((i>>1)&j)||((i<<1)&j)||((j<<1)&i)||((j>>1)&i);
}
int main(){
    
    cin>>n>>m>>p;
    
    init();
    
    for(int i=0;i<1<<n;i++){//枚举第1行
        
        f[1][i][0][st[i].n]++;
    }
    
    for(int i=0;i<1<<n;i++){//枚举第2行
        for(int j=0;j<1<<n;j++){
           for(int k=0;k<=p;k++){
               
               if(st[j].n>k) continue;
               
               if(jud(i,j)) continue;
               
               f[2][j][i][k]+=f[1][i][0][k-st[j].n];
               
           }
        }
    }
    
    for(int i=3;i<=m;i++){//枚举每一行
        for(int j=0;j<1<<n;j++){//i->st
            for(int k=0;k<1<<n;k++){//i-1->st
                
                if(jud(j,k)) continue;
                
                for(int z=0;z<1<<n;z++){//i-2->st
                    
                    if(jud1(j,z)||jud(k,z)) continue;
                    
                    for(int q=0;q<=p;q++){
                        
                        if(st[j].n>q) continue;
                        
                        f[i][j][k][q]+=f[i-1][k][z][q-st[j].n]%mod;
                        f[i][j][k][q]%=mod;
                    }
                }
            }
        }
    }
    
    for(int i=0;i<1<<n;i++){
        for(int j=0;j<1<<n;j++){
            
            if(jud(i,j)) continue;
            
            ans+=f[m][i][j][p];
            ans%=mod;
        }
    }
    
    cout<<ans%mod;
    
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值