其他分享
首页 > 其他分享> > 状压DP入门题【特殊方格棋盘】(部分格不能放) 题解

状压DP入门题【特殊方格棋盘】(部分格不能放) 题解

作者:互联网

 

(喵喵喵?

 

 

 

在学完状压dp后,有种听懂了又没听懂的感觉。。。第二天才做这题,借鉴了一下题解,在cjh大佬帮助下我终于给整明白了,特此写下我的第一篇题解。

 

 

题目描述

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

输入格式

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

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

输出格式

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

样例

样例输入

2 1
1 1

样例输出1

1
这个题的意思和八皇后很像,但是他让dfs找方案,所以dfs别想了(其实因为我是递归渣)
想到求方案,所以直接想到DP解决(其实是我刚学完状压dp)
所以就要用到状压dp

状压dp肯定都会罢?我就不说状压是啥了

别问我为啥直接上代码,我的思路和解释全在代码注释里,我是边做边写注释的,所以应该写的比较清楚。。

我觉得我码风应该不太差罢。。

代码思路来源于此篇题解:(im链接~)

#include<iostream>
#include<iomanip>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<cctype>
#include<algorithm>
#define ll long long
#define re register int
#define ull unsigned long long
#define mem(x,y) memset(x,y,sizeof(x))
#define sandom signed
#define INF (1 << 20)-1
using namespace std;
int n,m;
int cant[22];
ll f[INF];//cant存某一行不能放棋子的点,f[i]表示在i状态下的方案数
//因为方案数太多,所以疑心f盛不下 我不太清楚啊,按说2^31应该是够用的
inline int read(){
    int x = 0;char c;bool f = false;
    while(!isdigit(c = getchar())){
        if(c == '-'){
            c = getchar(),f = true;
            break;
        }
    }
    do{
        x = (x << 3) + (x << 1) + (c & 15);
    }while(isdigit(c = getchar()));
    
    if(f == true) return -x;
    return x;
}
int lowbit(int x){
    return x & (-x);
}
void work(){
    n = read(),m = read();
    for(re i = 1,hang,lie ; i <= m ; ++ i){
        hang = read(),lie = read();
        cant[hang] += 1 << (lie - 1);//画图
    }
    /*
    有一个点我澄清一下,这个状压的二进制表示,因为最低位在最右边,
    所以在真正表示的时候,右边对应着状态的起点:
    0 1 0 1 (二进制数)
    1 0 1 0 (状态表示)
    */
    f[0] = 1;//啥都不放也就是{0 0 0 0}的时候,也算一种方案

    for(re s = 1 ; s <= ((1 << n) - 1) ; ++ s){//枚举每个状态
        //要明白一个点,我们枚举的是状态而不是某一行
        /*
        枚举的状态就是指的是:
        {0 0 0 1},{0 0 1 0},{0 0 1 1},{0 1 0 0}......
        以此类推罢,没错和你想的一样,就是所有的放车的状态都枚举,这也是状压dp(n的数据范围)这么小的原因
        因为要从最初状态进行递推,所以这么着
        */
        int one_num = 0;
        for(re i = s ; i > 0 ; i -= lowbit(i)){//取当前状态的所有的1
            ++ one_num;
            /*对lowbit的解释:::
            学树状数组的时候lowbit就是直接用的不知道原理,这里我不再忍了,我来说一下lowbit
            lowbit的作用是从右到左取第一个遇到的1,就比如取{0 1 0 0 1 0}取的是第二位那个1
            首先,-x是取反加1,也就是补码,例子:
            原码:0 1 0 1 0 0
            取反:1 0 1 0 1 1 (符号位也变,就是处于最高位那一位(这个描述不太严谨,因为我不清楚符号位算不算'位'))
             +1 :1 0 1 1 0 0
            然后将-x和x进行按位与(&)操作
               0 1 0 1 0 0
            &  1 0 1 1 0 0
            ----------------
               0 0 0 1 0 0
            所以就得到了从右到左←←←得到的第一个1
            */
            /*对-=lowbit(i)的解释:::
            那这里我i-=lowbit(i)是甚麽意思呢?
            i不是一个状态吗,也就是这一行的状态
            所以我就这个one_num操作是通过-=lowbit(i){   来搞这一行一共有多少个1的   },因为一会要用:
            这个one_num表示的是到了第几行,你想啊,我们是想要这么干:每行都放一个,每列不能同时出现两个及以上,这样就可以完美地放下
            但是由于我们每一个放的位置不相同(既包括第一行放置位置,也包括以后的放置位置),所以会出现多种情况,这也是我们枚举每个状态的原因
            */
        }
        for(re i = s ; i > 0 ; i -= lowbit(i)){
            if((cant[one_num] & lowbit(i)) == 0){//找合法的放置位置,即从哪种子状态哪里转移过来
                int x = (s ^ (lowbit(i)));//枚举的某一种子状态
                f[s] += f[x];//大状态的方案数 += 子状态的方案数
                /*
                这个东西乍一见实际上是不懂的,但经过cjh大佬的耐心澄清后我理解了,在此说明一下:
                首先我们要明确这一步操作的目的,这一步操作是为了{   找到合法的放置位置   } 进一步由放置位置合不合法就等同于 {   把不合法的子状态给淘汰掉。   }
                Q:怎么淘汰?
                A:首先我们需要一种子状态来转移到当前枚举的状态s
                例如:大状态{0 1 0 1 1 0};子状态{0 1 0 1 0 0}、{0 1 0 0 1 0}、{0 0 0 1 1 0};
                所以我们要尝试每次刨去s中的某一个1,这样可以覆盖到(枚举到)每一个子状态,以此来达到枚举子状态的目的。
                那么回到原来,这个if里面啥意思呢?
                这个就是判断当前放置位置是否合法,以下是为什么这么判断是可以的:
                例如:
                当前枚举的状态s:
                     0 1 0 1 1 0
                当前表示的状态i://注意,i表示的不是子状态,而是删去某个点
                     0 1 0 1 0 0
                取lowbit后:
                     0 0 0 1 0 0
                cant数组:
                     0 0 0 1 0 0
                    把cant和取lowbit按位与,结果非0,那么说明当前这个点不能放
                那么如果按位与之后是0,那就说明当前这个点能放,反之不能
                淘汰了不合法的放置位置后就可以安心进行转移了
                */
            }
        }
    }
    printf("%lld",f[((1 << n) - 1)]);//把这个表示出来:
    /*
    n = 6,n:
    0 0 0 0 1 1 0
    但是我们最后要统计的当然是:
    0 1 1 1 1 1 1  (n=6,这种状态表示的是每一个地方都放好了(一共有n=6列))
    所以就是这样,1<<n:
    0 0 0 0 0 0 1
    进位n:
    1 0 0 0 0 0 0
    再减1:
    0 1 1 1 1 1 1
    好耶!
    */
}
sandom main(){
    work();
    return 0;
}

(内个,点赞这种事qwq......

 

标签:状态,题解,状压,枚举,放置,lowbit,include,DP,define
来源: https://www.cnblogs.com/charphi/p/16031227.html