互不侵犯

洛谷:P1896 [SCOI2005] 互不侵犯

这是牧心小辉的处女作。

这是牧心小辉第三次尝试 提高+/省选−题目,前段时间就偶尔刷刷水题,没有学习新的算法,希望大家不要学习俺。。。

这是一道主要考察状态压缩的题目,涉及到了许多技巧,题目比较难,但搞懂了也是蛮开心,有意义的一件事。

题目描述

在 N×N 的棋盘里面放 K 个国王,使他们互不攻击,共有多少种摆放方案。国王能攻击到它上下左右,以及左上左下右上右下八个方向上附近的各一个格子,共 8 个格子。

输入格式

只有一行,包含两个数 N,K。

在解这道题之前需要先了解位运算

好了,通过上图相信你对位运算有了一定的了解。

接下来如何有效的表示状态(花费较少的空间来存储状态)是一个问题:

本题采用的是用二进制表示状态,用十进制数存储状态

譬如以N=3为例:(1表示国王,0表示空位)

行的合法状态:000->0  001->1 010->2 100->4 101->5

行内合法判断:如果!(i&i>>1)为真,则i合法

行间兼容判断:如果!(a&b)&&!(a&b>>1)&&!(a&b<<1)为真,则兼容。

后面就是正常的dp了,类似多维背包,具体细节解释如下。

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std; 

int n,k;        //棋盘行数,国王总数
int cnt;        //一行的合法状态个数
int s[1<<12];   //一行的合法状态集
int num[1<<12]; //每个合法状态包含的国王数
long long f[12][144][1<<12];
//f[i,j,a]表示前i行已放了j个国王,第i行的第a个状态时的方案数 

int main(){
  cin>>n>>k;
  for(int i=0; i<(1<<n); i++) //枚举一行的所有状态
    if(!(i & i>>1)){          //如果不存在相邻的1
      s[cnt++]=i;             //一行的合法状态集,例101
      for(int j=0; j<n; j++)
        num[i]+=(i>>j & 1);   //每个合法状态包含的国王数
    }
  
  f[0][0][0]=1;                   //边界
  for(int i=1; i<=n+1; i++)       //枚举行
    for(int j=0; j<=k; j++)       //枚举国王数
      for(int a=0; a<cnt; a++)    //枚举第i行的合法状态
        for(int b=0; b<cnt; b++)  //枚举第i-1行的合法状态
        {
          int c=num[s[a]];        //第i行第a个状态的国王数
          if((j>=c)               //可以继续放国王
            &&!(s[b]&s[a])        //不存在同列的1
            &&!(s[b]&(s[a]<<1))   //不存在斜对角的1
            &&!(s[b]&(s[a]>>1)))
              f[i][j][a]+=f[i-1][j-c][b]; //行间转移
        }
  cout<<f[n+1][k][0]<<endl;       //第n+1行不放国王的方案数
  return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值