状态压缩DP

给出一个n*m 的棋盘(n、m≤80,n*m≤80),要在棋盘上放k(k≤20)个棋子,使得任意两个棋子不相邻,求可以得到多少种方案。

思路:这个题目的状态压缩模型是比较隐蔽的。观察题目给出的规模,n、m≤80,这个规模要想用SC 是困难的,若同样用上例的状态表示方法(放则为1,不放0),280 无论在时间还是在空间上都无法承受。然而我们还看到n*m≤80,这种给出数据规模的方法是不多见的,有什么玄机呢?能把状态数控制在可以承受的范围吗?稍微一思考,我们可以发现:9*9=81>80,即如果n,m 都大于等于9,将不再满足n*m≤80 这一条件。所以,我们有n 或m 小于等于8,而28 是可以承受的。我们假设m≤n(否则交换,由对称性知结果不变)n 是行数m 是列数,则每行的状态可以用m 位的二进制数表示。但是本题和例1 又有不同:例1 每行每列都只能放置一个棋子,而本题却只限制每行每列的棋子不相邻。但是,上例中枚举当前行的放置方案的做法依然可行。我们用数组s 保存一行中所有的num 个放置方案1,则s 数组可以在预处理过程中用DFS 求出,和另一种方法,可以看代码。同时用ci 保存第i 个状态中1 的个数以避免重复计算。开始设计状态。如例1 的注释所说,维数需要增加,原因在于并不是每一行只放一个棋子,也不是每一行都要求有棋子,原先的表示方法已经无法完整表达一个状态。我们用f[i][j,k 表示第i 行的状态为sj且前i 行已经放置了k 个棋子2的方案数。沿用枚举当前行方案的做法,只要当前行的方案和上一行的方案不冲突即可,“微观”地讲,即Ssid(i)和Ssid(i-1)没有同为1的位,其中sid(x)表示第x 行的状态的编号。然而,虽然我们枚举了第i 行的放置方案,但却不知道其上一行(第i-1 行)的方案。为了解决这个问题,我们不得不连第i-1 行的状态一起枚举,则可以写出递推式:f[i][s[j]][t]+=f[i-1][s[p]][t-c[j]](要求sj 与sp 不冲突)其中s1=0,即在当前行不放置棋子;j 和p 是需要枚举的两个状态编号。


#include<cstdio>
#include<iostream>
using namespace std;
const int INF=(1<<20)-1;
int num=0;
int n,m,maxn=0,sum=0;
int dp[81][1<<9][21]={0};
int s[1<<9];///每种状态的布置
int c[1<<9];///和s数组同步的状态的1的个数
///第一种处理每列的情况
/*void dfs(int state,int p,int sum)///状态,位置,1的个数
{
    while(p>m)///判断是否已到达列的尾部
    {
        num++;
        s[num]=state;
        c[num]=sum;
        return;
    }
    dfs(state,p+1,sum);///这个位置不放
    dfs(state+(1<<(p-1)),p+2,sum+1);///这个位置放置棋子,那么下一个紧邻的位置不可以放棋子
}*/
///第二种处理每列的情况
bool judge(int a,int b)
{
    return a&b;
}
void deal()
{
    for(int i=1;i<(1<<m);i++)
    {
        if(judge(i,i<<1))continue;
        {
            int k=i;
            while(k)
            {
                c[num]+=k&1;///该列的某种部署下1的个数,也就是炮兵的个数,和stk同步
                k=k>>1;
            }
            s[num++]=i;
        }
    }

}
int main()
{
    scanf("%d%d%d",&n,&m,&maxn);
    if(m>n)swap(n,m);
    ///dfs(0,1,0);
    deal();
    for(int i=1;i<=num;i++)
        dp[1][s[i]][c[i]]=1;
    for(int i=2;i<=n;i++)
    {
        for(int j=1;j<=num;j++)
        {
            for(int k=1;k<=num;k++)
            {
                if(s[j]&s[k])continue;
                {
                    for(int t=0;t<=maxn;t++)
                    {
                        if(t>=c[j])
                            dp[i][s[j]][t]+=dp[i-1][s[k]][t-c[j]];
                    }
                }
            }
        }
    }
    long long ans=0;
    for(int i=1;i<=num;++i)
        ans+=dp[n][s[i]][maxn]; //前n行放k个棋子,第n行选择状态s[i]的方案数相加
    printf("%I64d",ans);
    return 0;
}




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值