特殊方格棋盘——状态压缩dp

题面

在 n * n ( n <= 20 ) 的方格棋盘上放置n 个车,某些格子不能放,求使它们不能互相攻击的方案总数。

注意:同一行或同一列只能有一个车,否则会相互攻击

输入格式

输入文件第一行,有两个数n, m ,n表示方格棋盘大小,m表示不能放的格子数量

下面有m行,每行两个整数,为不能放的格子的位置。

输出格式

输出文件也只有一行,即得出的方案总数。

样例

样例输入

2 1
1 1

样例输出

1

 

分析

一如既往的,状态压缩动态规划。

限制分为两部分:棋盘本身的限制 , ”车(ju)“自身的限制。

 

我们使用a数组记录棋盘本身的限制即可,a [ x ]表示第x行限制情况所对应的十进制数字。 

 

这道题的有一个有意思的地方:一行里只能有一个车,一列里只能有一个车。

一行里只能有一个车,意思就是,我们可以只用一个二进制串s,就可以表示之前所有行(包括目前行)一共放置的车的状态了!

同时,这个限制串中"1"的个数,就是我们目前所在的行号!

特别注意,这个s串中,包括目前行的那个“1”!

 

比如我们有限制串:01101

那么我们目前处在第三行。那么我们具有如下的情况:

目前第三行的状态为:01000(即我在2号上放了一个车),则前两行的合状态为00101(前两行里分别在3号和5号位置上放了车)

目前第三行的状态为:00100(即我在3号上放了一个车),则前两行的合状态为01001(前两行里分别在2号和5号位置上放了车)

目前第三行的状态为:00001(即我在5号上放了一个车),则前两行的合状态为01100(前两行里分别在2号和3号位置上放了车)

则我们定义f [ s ]为:在s串的状态下,一共可能的放置方案数目。

注意,s本身就携带着“阶段(行号)”和“状态”两层信息,所以动规数组 f 只用一维就可以了。

 我们再用Xi表示以前行的所有可能的合状态(就是例子里我标蓝的部分,X1,X2,X3)

则有 f [ s ] = f [ X1 ] + f [ X2 ] +f [ X3 ] …… f [ Xi ]

 

那么如何实现是一个问题。我们有这个函数:lowbit ( x )

如果不知道的话,可以百度。我在这里简单说几句

lowbit ( x ) 返回的是一个数x(二进制形式)中,最低位的1,与其后的所有0,组成的的数值。

举例一:100001110100111000

  该数字的lowbit值为“1000”,即8

举例二:10001010

  该数字的lowbit值为“10”,即2

举例三:1001

  该数字的lowbit值为“1”,即1

实现方式

 

int lowbit(int x){return (x&(-x));}

为什么这么做可以?利用了负数使用补码储存的原理。具体的自己去百度吧。

 

使用lowbit函数,我们就可以使用这样的方式来统计,s数串里“1”的个数,以获取它所携带的行号信息:第“cnt”行

for(int i=s;i;i-=lowbit(i))cnt++;//当前是第cnt行

 

在执行那个标蓝部分,和动态转移的时候,我们使用这样一种巧妙的方式来进行寻找s所有子序列的工作

for(int i=s;i;i-=lowbit(i)){
    if(!(a[cnt] & lowbit(i))){
        int x=s^lowbit(i);
        f[s]+=f[x];
    }
}

我们模拟一下:假设当前s串为“ 010110010”

那么开始:

 

i 记录:010110010;lowbit(i)=10,这个就是我们目前假设的本行车所在的位置。

“如果该行障碍 a [ cnt ]条件允许”,那么我们将原来s串010110010,中的“10“消去,变为010110000,记录为x。这个x就是之前行的合状态。

将 f [ s ] 加上这一种之前合状态的数目。

 

 i 减去 10,i记录:010110000;lowbit ( i ) =10000,这个就是我们目前又假设的本行车所在位置的另一种情况。

“如果该行障碍 a [ cnt ]条件允许”,那么我们将原来s串010110010,中的“10000“消去,变为010100010,记录为x。这个x就是之前行的合状态。

将 f [ s ] 加上这一种之前合状态的数目。

 

i 减去 10000,i记录:010100000;lowbit ( i ) =100000,这个就是我们目前又假设的本行车所在位置的另一种情况。

“如果该行障碍 a [ cnt ]条件允许”,那么我们将原来s串010110010,中的“100000“消去,变为010010010,记录为x。这个x就是之前行的合状态。

将 f [ s ] 加上这一种之前合状态的数目。

 

i 减去 100000,i记录:010000000;lowbit ( i ) =10000000,这个就是我们目前又假设的本行车所在位置的另一种情况。

“如果该行障碍 a [ cnt ]条件允许”,那么我们将原来s串010110010,中的“10000000“消去,变为000110010,记录为x。这个x就是之前行的合状态。

将 f [ s ] 加上这一种之前合状态的数目。

 

i 减去10000000,i记录:0循环停止。

这时候 f [ s ] 就是本s串的答案了!

 

那么再提醒一个点。根据我之前写的那个玉米田的题,我们既然有n列,那么每一行就会有2^n -1种不同的s串 。

不同的是,由于我们这回首先预处理: f [ 0 ] =1,即一个不放也是一种情况,所以这回我们s串映射的就不再是0到2^n-1了,而是1到2^n 

不过最后输出的时候,记得输出 f [ 2^n -1 ]

而且注意longlong的问题。数目比较大,使用longlong。

 

代码 

 

#include<cstdio>
#include<algorithm>
#include<cstring>
const int maxn=1<<20+1;
typedef long long ll;
ll f[maxn],a[25];//a记录行障碍状态
int lowbit(int x){return (x&(-x));}
int main(){
    int n,m;scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++){
        int x,y;scanf("%d%d",&x,&y);
        a[x]+=(1<<(y-1));
    }
    f[0]=1;//一个不放也是一种方案
    int maxs=1<<n;
    for(int s=1;s<=maxs;s++){
        int cnt=0;
        for(int i=s;i;i-=lowbit(i))cnt++;//当前是第cnt行
        for(int i=s;i;i-=lowbit(i)){
            if(!(a[cnt] & lowbit(i))){
                int x=s^lowbit(i);
                f[s]+=f[x];
            }
        }
    }
    printf("%lld\n",f[maxs-1]);
    return 0;
}

 

祝AC

 

posted @ 2020-04-16 10:43  Dertangch  阅读(341)  评论(0编辑  收藏  举报