【引言】:
首先位运算是状态压缩DP的基础(是不是基础不清楚,反正是用到了)。所以在引言首先来解释一下或者通俗的说,来写一下,某种的操作怎么打罢了
位运算首先就是建立在计算机二进制上的操作罢了。
有四种操作,就是 $(与),|(或),^(异或),~(取反),<<(左移),>>(右移),
1. ,& ,两 个 位 都 为 1 时 , 结 果 才 为 1
2. ,| ,两 个 位 都 为 0 时 , 结 果 才 为 0
3. , ^ ,两 个 位 相 同 为 0 , 相 异 为 1 (这就是造反,不一样就干仗)
4. , ~ ,0 变 1 , 1 变 0
5. , << , 将数字(num)的二进制向左移,并将移动到的后面那一些数字全部置换成 0
6. ,>> , 同 <<,显然
需要的时候及时补充
1.
【例题1】: 互不侵犯
【题目大意】:
在一个(n*n)的图中选(k)个点,使得以每个点为中心的九宫格内有且只有它一个点,问方案数
【思路分析】
首先我们分析对于一个是否可以被放置的条件就是他前后左右左上左下右上右下都不能放置其他的点,但是我们放置的时候我们只需要考虑这个点的左上,左边,和上方就(OK)了,因为相对的,我们在下几个点进行放置的时候考虑一下这三个方向等同于这个点考虑其他方向,所以就很好理解了,十分的显然,画个图就出来,我是不会告诉你,学长讲完之后我一脸懵逼的,既然现在我们的放置条件(OK)了,同时,我们也需要记录一下上一个状态,也就是不能够直接枚举每一行,和做其他(DP)一样,而状态压缩DP要的就是状态,(把状态省去了叫什么状态压缩(DP)),
【状态设计】
设(f_{i,j,k})表示已经选择完前(i)行,第(j)个状态,上一行状态为(k)的点的方案数,
为什么要这么设计状态,其实前面已经说过了,我们现在放置点数不可以超过(k)个,自然,也不可以小于(k)个,同时为了判断一下这个点是否可以被放置,我们设及(j)状态,(i)行就很显然了
【状态转移】
每一行肯定是可从上一行转移而来,那么,我们(j)记录了上一行的状态,
同时我们需要知道,(j)这个状态这是一个二进制数,我们需要将它转化成我们需要的状态,
例如,当(n==4)时,上一行的状态是 (1) (n==4)的时候,也就是 (0001),也就是说只有最后一个位置放置国王,同理,状态为(4) 也就是(0100)那么也就是说明,第三个位置放置了国王了,同时,这也是我们设计(j)状态的时候关于它的数组,应该开到(2^n-1),
回到正题,转移的规则自然也是有的,
1.当前一行的国王不能在上一行的左下,右下,正下方。
2.当前一行的国王不能有相邻的,不然就会被攻击到
状态转移方程很显然:
(f_{i,j,k}=f_{i-1,s,k-j.num}) ,其中(s)表示的是上一行的那个状态,(j.num)也就是这一个状态的1的个数
(emmm,QwQ),就这样
【代码实现】
1.先预处理出每一个状态的个数1有多少个
2.预处理第一行的实现,对于每一个状态和每一个状态1的个数,都是直接放置
3.(DP)
4.最后的 (ans = sum limits_{0≤jleq cnt(状态数)} f_{n,j,k})
5.return 0,//好习惯
【code】
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define int long long
#define inf 0x3f
#define lowbit(x) x&(-x)
const int N = 1e6;
const int M = 1e5;
inline int read()
{
int x=0 , f=1; char ch=getchar();
while( !isdigit(ch) ){ if(ch == '-')f = -1; ch = getchar();}
while( isdigit(ch) ){ x = x *10 + ch - '0';ch = getchar();}
return x * f;
}
int n,k;
int f[20][513][513];
struct node
{
int val,num;
}a[N];
int check(int x,int y)
{
if( x & (x<<1)) return 0;
int cnt = 0;
for(int i = x;i ;i -= lowbit(i))
{
cnt ++ ;
}
if(cnt<=k) return cnt;
else return 0;
}
signed main()
{
n = read(), k = read();
int cnt = 1;
for(int i = 1; i < (1<<n); i++)
{
int sum = check(i,k);
if(sum)
{
a[++cnt].val = i;
a[cnt].num = sum;
}
}
for(int i = 1;i <= cnt;i++ )
{
f[1][a[i].val][a[i].num] = 1;
}
for(int i = 2;i <= n; i++)
{
for(int j = 1;j <= cnt ;j++)
{
for(int s = 1;s <= cnt; s++)
{
if( (a[j].val & a[s].val) == 0 && (a[j].val & (a[s].val << 1))==0 && ( a[j].val & (a[s].val>>1)) == 0 )
{
for(int p = a[j].num; p <=k; p ++)
{
f[i][a[j].val][p] += f[i-1][a[s].val][p-a[j].num];
}
}
}
}
}
int ans = 0;
for( int i = 1;i <=cnt ;i++)
{
ans += f[n][a[i].val][k];
}
printf("%lld",ans);
return 0;
}
【例题2】P1879 [USACO06NOV]Corn Fields G
【题目大意】:
无,看一下链接吧
【思路分析】:
首先看到 (n,m leq 12) , 不是状态压缩DP,就是记忆化搜索。
题目要求的是求解方案数,和上面的要求差不多,但是比上面要满足的条件少,所以相比之下,这个更为简单,思路尽量模仿上面的即可
【状态设计】:
设 (f_{i,j})表示的是前(i)行,状态为(j)的方案数;这个题不需要看放置多少的牛,所以没有第3维。对于上一行的状态,可以通过(i-1)然后枚举一遍(k,k in [0,2^n-1]),就可以了
【状态转移】:
由于这个题不需要考虑放置的多少,那么也就是只要是这一行没放置的,就给它放上去,即为(f_{i,j} = f_{i-1,k} k in [0,2^n-1])同时满足 ,上一行已经放了的这次不能放了,即为$j& and (k) (=0)
【代码实现】:
1.讲地形读入
2.(mark_i)表示的是第(i)行的地形的状态
3.只要是两边的状态和中间的状态冲突,直接处理掉,可以省下在(DP)中再次判断这个状态是否合法
4.(DP)
5.(ans = sum f_{n,i},i in [0,2^n-1])
- return 0; // 好习惯
【(code)】:
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define int long long
#define lowbit(x) x&(-x)
const int mod = 100000000;
const int inf = 0x3f3f3f3f;
inline int read()
{
int x=0 , f=1; char ch=getchar();
while( !isdigit(ch) ){ if(ch == '-')f = -1; ch = getchar();}
while( isdigit(ch) ){ x = x *10 + ch - '0';ch = getchar();}
return x * f;
}
int m , n;
int f[20][(1<<12)];
int a[100][100];
int mark[13];
int caq_0[3000];
signed main()
{
m = read() , n = read();
for(int i = 1; i <= m; i++)
{
for(int j = 1; j <= n; j++)
{
a[i][j] = read();
}
}
for(int i = 1; i <= m ; i++)
{
for(int j = 1; j <= n ; j++)
{
mark[i] = ( mark[i] <<1 ) + a[i][j];
}
}
int max_caq = (1 << n);
for(int i = 0 ; i < max_caq ; i++)
{
caq_0[i] = ( ( (i& (i<<1) ) == 0 )&& ( (i&(i>>1)) == 0 ));
}
f[0][0] = 1;
for(int i = 1; i <= m; i++)
{
for(int j = 0; j < max_caq ;j++)
{
if( ((j & mark[i]) == j) && caq_0[j])
{
for(int k = 0 ;k < max_caq ; k++)
{
if( ( k & j ) == 0)
{
f[i][j] = ( f[i][j] + f[i-1][k] )%mod;
}
}
}
}
}
int ans = 0;
for(int i = 0; i < max_caq; i++)
{
ans += f[m][i] ;
//printf("%ld
",ans);
ans %=mod;
}
printf("%ld",ans);
return 0;
}