DP状态压缩之棋盘问题

Posted ThXe

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了DP状态压缩之棋盘问题相关的知识,希望对你有一定的参考价值。

写在前面

状态压缩的思想是将难以用机器语言表达具象的现实实物进行抽象.常常用二进制数来表达一种状态比方说有A、B、C,三个方案,2=010表示不选A,选B,不选C的方案,于是要枚举所有方案只需要枚举0-1<<n的自然数,看每一位上是0还是1即可。这种思想经常配合其他算法来出难题,今天给大家带来的是状态压缩与DP的一类问题。

P.A 玉米地题目链接

农夫约翰的土地由 M×N 个小方格组成,现在他要在土地里种植玉米。

非常遗憾,部分土地是不育的,无法种植。

而且,相邻的土地不能同时种植玉米,也就是说种植玉米的所有方格之间都不会有公共边缘。

现在给定土地的大小,请你求出共有多少种种植方法。

土地上什么都不种也算一种方法。

输入格式
第 1 行包含两个整数 M 和 N。

第 2…M+1 行:每行包含 N 个整数 0 或 1,用来描述整个土地的状况,1 表示该块土地肥沃,0 表示该块土地不育。

输出格式
输出总种植方法对 108 取模后的值。

数据范围
1≤M,N≤12
输入样例:
2 3
1 1 1
0 1 0
输出样例:在这里插入代码片
9

算法分析

题目的主要限制有两个方面,一个是土壤可能不能种植,而是玉米直接的相邻情况。
我们尝试用二进制来表达没一行的玉米种植情况。0代表不种,1代表种,考虑同一行的情况,该二进制数不能有相邻的两个1,考虑上一行的情况,每一位的对应位置自然也不能有相邻的1。

与前言类似,我们用0-1<<n每一个数字来枚举所有方案
假设没有土地限制对于每个方案我们进行筛选
1.相邻两位上不含1的数为一行合法状态

bool check(int x)
{
    return !(x&x>>1);
}

2.对于每一行合法状态,对其拓展下一行合法即为判断上下两行是否有1

for(int i=0;i<state.size();i++)
    for(int j=0;j<state.size();j++)
    {   
        int a=state[i],b=state[j];
        if((a&b)==0) h[i].push_back(j);
    }

3.判断与地图情况是否符合即土地为0的地方,这一位方案为1

 if((g[i]&state[j])==0)
        {
            for(auto k:h[j])
            {
                f[i][j]=(f[i][j]+f[i-1][k])%mod;
            }
        }

4.状态表达用f[i][j],表示已经排完i行且第i行状态为j的方案数很自然的得到状态转移方程

 for(auto k:h[j])
            {
                f[i][j]=(f[i][j]+f[i-1][k])%mod;
            }

ACcode

#include<bits/stdc++.h>
using namespace std;
int n,m;
const int N=1<<12,mod=1e8;
int g[15],f[15][N];
vector<int>state;//相邻位不含1的方案
vector<int>h[N];//h[i]储存所有状态i可以变化的合理方案j
bool check(int x)
{
    return !(x&x>>1);//O(1)判断一个数是否有两个相邻1
}
int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++)
    for(int j=0;j<m;j++)
    {   
        int t;
        cin>>t;
        g[i]+=!t*(1<<j);
    }
    for(int i=0;i<1<<m;i++)
    {
        if(check(i))state.push_back(i);
    }
    for(int i=0;i<state.size();i++)
    for(int j=0;j<state.size();j++)
    {   
        int a=state[i],b=state[j];
        if((a&b)==0) h[i].push_back(j);
    }
    f[0][0]=1;

    for(int i=1;i<=n+1;i++)
    for(int j=0;j<state.size();j++)
    {
        if((g[i]&state[j])==0)
        {
            for(auto k:h[j])
            {
                f[i][j]=(f[i][j]+f[i-1][k])%mod;
            }
        }
    }
    cout<<f[n+1][0];
}

PB 小国王题目链接

在 n×n 的棋盘上放 k 个国王,国王可攻击相邻的 8 个格子,求使它们无法互相攻击的方案总数。

输入格式
共一行,包含两个整数 n 和 k。

输出格式
共一行,表示方案总数,若不能够放置则输出0。

数据范围
1≤n≤10,
0≤k≤n2
输入样例:
3 2

输出样例:
16

算法分析

与上一题类似,限制条件少了地图限制,多了每行之间的限制我们将上下行的合理转移修改为

for(int i=0;i<state.size();i++) 
    for(int j=0;j<state.size();j++)
    {
        int a=state[i],b=state[j];
        if((a&b)==0&&(check(a|b)))
        {
            head[i].push_back(j);
        }
    }

状态表示

这题多了个限制是k个国王,于是将状态多开一维见代码注释

ACcode

#include<bits/stdc++.h>
using namespace std;
const int N=1<<10;
long long f[12][110][N];//第一维代表第i行,第二维代表已经有j个国王,第三维代表方案状态
vector<int>state;
vector<int>head[N];
int cnt[N];
int n,k;
void count(int x)//计算每个二进制数中1的个数
{
    for(int i=0;i<n;i++)
    if(x>>i&1)cnt[x]++;
}
bool check(int x)
{
    return !(x&x>>1);
    
}
int main()
{
    cin>>n>>k;
    for(int i=0;i<1<<n;i++)
    {
        if(check(i))
        {
            state.push_back(i);
            count(i);
        }
    }
    for(int i=0;i<state.size();i++) 
    for(int j=0;j<state.size();j++)
    {
        int a=state[i],b=state[j];
        if((a&b)==0&&(check(a|b)))
        {
            head[i].push_back(j);
        }
    }
    f[0][0][0]=1;
    for(int i=1;i<=n+1;i++)
    for(int j=0;j<=k;j++)
    for(int a=0;a<state.size();a++)
    for(auto b:head[a])
    {
        int c=cnt[state[a]];
        if(j>=c)f[i][j][a]+=f[i-1][j-c][b];
    }
    cout<<f[n+1][k][0];
    
}

以上是关于DP状态压缩之棋盘问题的主要内容,如果未能解决你的问题,请参考以下文章

状态压缩dp 入门

炮(棋盘DP)

poj 1191 棋盘切割 (压缩dp+记忆化搜索)

状态压缩DP 初探

C++之路进阶——状态压缩dp(互不侵犯)

状压DP总结