状压dp hdu-4529-郑厂长系列故事——N骑士问题

题目链接:

http://acm.hdu.edu.cn/showproblem.php?pid=4529

题目意思:

有一8*8的矩阵,放了8个皇后,让你放n个骑士,使骑士之间不能相互攻击(走日字)。

解题思路:

状态压缩 dp.

dp[i][j][p][q]:表示第i行状态为q(q写成二进制时,1表示放骑士,0表示不放骑士),第i-1行状态为p,并且前i行总的骑士的个数为j时总的方案总数。

对每一行,枚举每一种状态,依据前两行的状态,进行转移。

代码:

#include<iostream>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<string>
#include<cstring>
#include<algorithm>
#include<vector>
#include<map>
#include<stack>
#include<list>
#include<queue>
#define eps 1e-6
#define INF (1<<30)
#define PI acos(-1.0)
using namespace std;

#define ll __int64
#define Maxn 60
ll dp[2][11][1<<8][1<<8]; //dp[i][j][p][q] 表示第i行放马的状态为q,
                  //第i-1行放马的状态为p,前i行放马的总个数为j个的总方案数

bool flag1[1<<8][1<<8],flag2[1<<8][1<<8];

bool iscan(int a,int b,int d) //判断状态a和状态b之差是不是相隔d列的
{
   for(int i=0;i+d<8;i++)
   {
      int x=1<<i;
      int y=1<<(i+d);

      if((a&x)&&(b&y))
         return false;
      if((b&x)&&(a&y))
         return false;
   }
   return true;
}

int num[11];//统计每一行中皇后所在位置的状态
int cnt[1<<8]; //每种状态中1的个数
char save[11];

int cal(int x) //统计该状态下含1的个数
{
   int res=0;

   while(x)
   {
      res+=(x&1);
      x=x>>1;
   }
   return res;
}

int main()
{
   for(int i=0;i<(1<<8);i++)
   {

      for(int j=0;j<(1<<8);j++)
      {
         flag1[i][j]=iscan(i,j,1); //预处理下相隔为1的情况
         flag2[i][j]=iscan(i,j,2); //预处理下相隔为2的情况
      }
      cnt[i]=cal(i);
   }

   int t,n;

   scanf("%d",&t);
   while(t--)
   {
      scanf("%d",&n);
      for(int i=0;i<8;i++)
      {
         scanf("%s",save);
         num[i]=0;
         for(int j=0;j<8;j++) //计算皇后的位置状态
         {
            num[i]<<=1;
            if(save[j]=='*')
               num[i]++;
         }
      }
      memset(dp,0,sizeof(dp));
      //先预处理第一行的情况
      for(int i=0;i<(1<<8);i++)
      {
         if(cnt[i]>n||(num[0]&i)) //有皇后的位置不能放
            continue;
         dp[0][cnt[i]][0][i]++;
      }

      int cur;
      for(int i=1;i<8;i++) //滚动数组,注意每一步都要清零
      {
         cur=i&1;
         for(int j=0;j<(1<<8);j++)
            for(int k=0;k<(1<<8);k++)
               for(int nn=0;nn<=n;nn++)
                  dp[cur][nn][j][k]=0;

         for(int j=0;j<(1<<8);j++)
         {
            if((i-2)>=0&&(num[i-2]&j)) //不能放
               continue;
            for(int k=0;k<(1<<8);k++)
            {
               if(num[i-1]&k)  //不能放
                  continue;
               if(cnt[j]+cnt[k]>n)
                  continue;
               if(flag2[j][k]==false)
                  continue;

               int tt=cnt[j]+cnt[k];

               for(int p=tt;p<=n;p++) //j表示i-2行的状态,k表示i-1行的状态
               {
                  if(dp[cur^1][p][j][k]==0) //前面两行就不能满足了
                     continue;
                  for(int z=0;z<(1<<8);z++) //枚举当前行的状态,建立转移方程
                  {
                     if(!flag2[k][z]||!flag1[j][z])
                        continue;
                     if(z&num[i])
                        continue;
                     if(p+cnt[z]>n)
                        continue;
                     dp[cur][p+cnt[z]][k][z]+=dp[cur^1][p][j][k];
                  }
               }
            }
         }

      }
      ll ans=0; //统计出答案
      for(int i=0;i<(1<<8);i++)
         for(int j=0;j<(1<<8);j++)
            ans+=dp[cur][n][i][j];
      printf("%I64d\n",ans);

   }

   return 0;
}


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值