P1896 [SCOI2005]互不侵犯 状压dp 经典入门题

版权声明:点个关注(^-^)V https://blog.csdn.net/weixin_41793113/article/details/89670564

题目描述

在N×N的棋盘里面放K个国王,使他们互不攻击,共有多少种摆放方案。国王能攻击到它上下左右,以及左上左下右上右下八个方向上附近的各一个格子,共8个格子。

注:数据有加强(2018/4/25)

输入输出格式

输入格式:

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

输出格式:

所得的方案数

输入输出样例

输入样例#1: 复制

3 2

输出样例#1: 复制

16

首先,看到这一题,就知道如果不是搜索,就是DP。当然搜索是过不了的,所以就应该尝试想出一个DP的解法。

DP的前提之一当然是要找出一个可以互相递推的状态。显然,目前已使用的国王个数当然必须是状态中的一个部分,因为这是一个限制条件。那么除此之外另外的部分是什么呢?

我们考虑到每行每列之间都有互相的约束关系。因此,我们可以用行和列作为另一个状态的部分(矩阵状压DP常用行作为状态,一下的论述中也用行作为状态)。

又看到数据范围: 1 <=N <=9。这里我们就可以用一个新的方法表示行和列的状态:数字。考虑任何一个十进制数都可以转化成一个二进制数,而一行的状态就可以表示成这样——例如:

10101010(2)

就表示:这一行的第一个格子没有国王,第二个格子放了国王,第三个格子没有放国王,第四个格子放了国王(注意,格子从左到右的顺序是与二进制从左到右的顺序相反的,因为真正在程序进行处理的时候就像是这样的)。而这个二进制下的数就可以转化成十进制:

1010(10)

于是,我们的三个状态就有了:第几行(用i表示)、此行放什么状态(用j表示)、包括这一行已经使用了的国王数(用s表示)。

考虑状态转移方程。我们预先处理出每一个状态(sit[x])其中包含二进制下1的个数,及此状态下这一行放的国王个数(gs[x]),于是就有:

f[i][j][s]=sum(f[i-1][k][s-gs[j]]),f[i][j][s]就表示在只考虑前i行时,在前i行(包括第i行)有且仅有s个国王,且第i行国王的情况是编号为j的状态时情况的总数。而k就代表第i-1行的国王情况的状态编号

其中k在1到n之间,j与k都表示状态的编号,且k与j必须满足两行之间国王要满足的关系。(对于这一点的处理我们待会儿再说)

这个状态转移方程也十分好理解。其实就是上一行所有能够与这一行要使用的状态切合的状态都计入状态统计的加和当中。其中i、j、s、k都要枚举。

再考虑国王之间的关系该如何处理呢?在同一行国王之间的关系我们可以直接在预处理状态时舍去那些不符合题意的状态,而相邻行之间的关系我们就可以用到一个高端的东西:位运算。由于状态已经用数字表示了,因此我们可以用与(∧)运算来判断两个状态在同一个或者相邻位置是否都有国王——如果:

sit[j]sit[j]&sit[k]sit[k](及上下有重复的king)

(sit[j]<<1)&sit[k](及左上右下有重复king)

sit[j]&(sit[k]<<1)(及右上左下有重复king)

这样就可以处理掉那些不符合题意的状态了。

总结一下。其实状压DP不过就是将一个状态转化成一个数,然后用位运算进行状态的处理。理解了这一点,其实就跟普通的DP没有什么两样了。

#include<iostream>
#include<cstdio>
using namespace std;

typedef long long ll;
const int MAX = 10;
ll dp[MAX][(1<<MAX)+5][MAX*MAX];
ll state[(1<<MAX)+5],king[(1<<MAX)+5];
int n,k,cnt=0;


void init(){
    int tot = (1<<n)-1;
    for(int i=0;i<=tot;i++)
        if(!((i<<1)&i)){//预处理不冲突的状态
            state[++cnt] = i;
            int x = i;
            while(x>0){
                king[cnt]+=x%2;
                x>>=1;
            }

        }



}


int main(){
    while(~scanf("%d%d",&n,&k)){
        init();
        for(int i=1;i<=cnt;i++)
            //if(king[i]<=k)//防越界,其实没必要
                dp[1][i][king[i]] = 1;//初始化第1行的所有状态初值
        for(int i=2;i<=n;i++)
            for(int j=1;j<=cnt;j++)
                for(int p=1;p<=cnt;p++){
                    if(state[j] & state[p])//相等于j==p,什么都不干就跳了吧
                        continue;
                    if((state[j]<<1) & state[p])//相等于j==p,什么都不干就跳了吧
                        continue;
                    if((state[j]) & (state[p]<<1))//相等于j==p,什么都不干就跳了吧
                        continue;
                    for(int s=1;s+king[j]<=k;s++)
                        dp[i][j][s+king[j]] += dp[i-1][p][s];
            }

        ll sum=0;
        for(int i=1;i<=n;i++)
            for(int j=1;j<=cnt;j++)
                sum += dp[i][j][k];
        printf("%lld\n",sum);
    }

	return 0;
}

猜你喜欢

转载自blog.csdn.net/weixin_41793113/article/details/89670564