状压DP

 [SCOI2005]互不侵犯

在N×N的棋盘里面放K个国王,使他们互不攻击,共有多少种摆放方案。

  国王能攻击到它上下左右,以及左上左下右上右下八个方向上附近的各一个格子,共8个格子。

输入格式:

只有一行,包含两个数n,m ( 1 <=n <=9, 0 <= m<= N * N)

输出格式:

所得的方案数

 

初学者很天真地想写爆搜,但总感觉不妥,然后就听说这是状压DP……

这怎么DP呀,要记录上一行是什么样吗?如何记录这么多状态?

这就是状态压缩要解决的问题。

对于每一行我们考虑用bool数组记录每一格的情况,1表示有,0表示没有,例如:10010101,表示第一,四,六,八格有国王。

那么我们可以发现这个东西可以看做一个8位二进制数149,别告诉我149存不下。

所以我们就可以将一行的状态压缩成一个二进制数,而处理这些状态就显然需要用位运算了。

关于基础的位运算我们就不在赘述,这里介绍几个状压DP中常用的操作。

S&(1<<i):判断第i位的情况,是0还是1.拿这道题来说就是状态S的第i格是不是有了国王。(需要注意的是i是从右往左数的)

S|(1<<i):将S的第i位设置为1,往i这一位放上国王。

S|~(1<<i):将S的第i位设置为0,往i这一位拿下国王(虽然这里不用)

S1&S2:判断S1和S2是否有交集(对于某一位或几位i,有S1[i]==S2[i]==1),这里可以判断上下两行的国王会不会冲突。

S1&(S2<<1):将S2左移一位再进行按位与,此时S1和S2是错位的,就可以用来判断右上和左下的冲突。

S1&(S2>>1):同上,判断左上和右下的冲突。

注:弄不清运算符优先级的同学们一定要多加括号。

 

科普完我们就该讲一下这道题的思路了(^_^),我们令f[i][j][k],表示第i行,状态为j且前i行已放置k个国王的方案数。

那么显然f[i][j][k]=∑f[i-1][j′][k′]

其中k=k'+状态j的国王数。

最终ans=Σf[n][i][m]

我们可以预处理出所有状态以及该状态的国王数。

代码如下:

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
int n,m;
long long f[10][150][150],ans;
int num[150],s[150],total;
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=0;i<(1<<n);i++)
    {
        if(i&(i<<1))continue;//检查当前状态是否冲突 
        int k=0;
        for(int j=0;j<n;j++)
        if(i&(1<<j))//数一数这个状态需要多少国王 
        k++;
        s[++total]=i;//记录状态 
        num[total]=k;//国王个数 
    }
    f[0][1][0]=1;
    for(int i=1;i<=n;i++)
    for(int j=1;j<=total;j++)
    for(int k=0;k<=m;k++)
    if(k>=num[j])
    for(int t=1;t<=total;t++)//第i-1行可能是什么 
    if(!(s[t]&s[j])&&!(s[t]&(s[j]<<1))&&!(s[t]&(s[j]>>1)))//上下无冲突。 
    f[i][j][k]+=f[i-1][t][k-num[j]];
    for(int i=1;i<=total;i++)
    ans+=f[n][i][m];
    cout<<ans;
}

 请原谅本蒟蒻现学现卖QWQ

 

然后我又找来了另一道题:

[USACO06NOV]玉米田Corn Fields

 

貌似也是一道裸题,那不妨我们就用这道题来总结套路:

令f[i][j],表示第i行j种状态的方案数。

首先预处理每一行可能的状态,我们要保证左右不重复,且不能种在贫瘠的土地上。

对于每一行i的每一种状态j,考虑前一行的每种状态k,若不冲突,则令f[i][j]+=f[i-1][k].

最终统计所有f[n],(习惯上令n表示行数,m表示列数)

这就是刚学习状压的蒟蒻的感受:

先预处理状态,对于每种状态考虑是否与前一状态重复并递推答案。最后统计终点的所有状态的答案之和。

 

 1 #include<iostream>
 2 #include<algorithm>
 3 #include<cstring>
 4 #include<cstdio>
 5 using namespace std;
 6 const int mod=100000000;
 7 int n,m,f[20][1<<20];
 8 struct cym{
 9     int s[1<<20],num;
10 }a[15];
11 void find(int S,int now)
12 {
13     int total=0;
14     for(int i=0;i<(1<<m);i++)
15     if(!(i&(i<<1))&&!(i&(i>>1))&&!(i&S))//我们可以用左右移与自身匹配来判断左右冲突。 
16     a[now].s[++total]=i;//now来表示土地情况,状态不与now冲突。 
17     a[now].num=total;
18 }
19 int main()
20 {
21     scanf("%d%d",&n,&m);
22     for(int i=1;i<=n;i++)
23     {
24         int now=0;
25         for(int j=1;j<=m;j++)
26         {
27             int x;
28             scanf("%d",&x);
29             now=(now<<1|1)-x;
30         }
31         find(now,i);
32     }
33     for(int i=1;i<=a[1].num;i++)
34     f[1][i]=1;
35     for(int i=2;i<=n;i++)
36     for(int j=1;j<=a[i].num;j++)
37     for(int k=1;k<=a[i-1].num;k++)
38     if(!(a[i].s[j]&a[i-1].s[k]))//与前一状态是否冲突 
39     {
40         f[i][j]+=f[i-1][k];
41         f[i][j]%=mod;
42     }
43     int ans=0;
44     for(int i=1;i<=a[n].num;i++)
45     ans=(ans+f[n][i])%mod;
46     printf("%d",ans);
47 }

 

posted @ 2018-06-25 10:58  _ZZH  阅读(149)  评论(0编辑  收藏  举报