状压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