状压DP入门经典题 洛谷P1896 互不侵犯

题目链接:https://www.luogu.org/problem/P1896

题意:给你一个n*n的棋盘,摆上k个国王,要求每个国王九宫格范围内没有别的国王

分析:状压DP入门经典题

我们可以用二进制数串表示状态比如1010 就是该行第二个第四个有国王,第一三个就没有

判断九宫格范围内是否有别的国王,我们从上到下依次递推

对于当前行,就是i&(i<<1)不为0的话,就说明相邻的有

要特别注意s的起点,如果不能理解就直接记住从0开始的做法就好

具体的讲解在代码里面

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int inf=0x3f3f3f3f;
const int mod=1e9+7;
const int maxn=1e3+7;
const int N=1e4;
int n,k;
int state[1500],king[1500];//分别表示当前状态,以及当前状态的国王数 
ll dp[15][1500][100];//dp[i][j][k]表示第i行,状态j,总共用了k个国王的方案数 
ll res,ans;
void init(){
    int tot=1<<n;//左移n位,遍历的时候记住遍历到tot-1,比如5个数位我们用64来表示,63即11111
    for(int i=0;i<tot;i++){
        if(i&(i<<1))continue;//有值说明当前行不满足左右不相邻
        state[++res]=i;
        int temp=i;
        while(temp){
            if(temp%2)king[res]++;
            temp>>=1;
        } 
    } 
} 
int main(){
    scanf("%d%d",&n,&k);
    init();//初始化,求出满足本行内不相邻的情况(状态) 
    for(int i=1;i<=res;i++){//初始化第一行 
        if(king[i]<=k)dp[1][i][king[i]]=1;
    }
    for(int i=2;i<=n;i++)//从第二行开始一行行的找 
        for(int j=1;j<=res;j++){//枚举当前行状态 
            for(int p=1;p<=res;p++){//枚举上一行的状态 
                //因为要求一个国王九宫格内不允许有另外的国王,具体到上一行就是上一行的正上,左上,右上都不能有国王(也就是状态数不能有1)
                //故记下来三个操作分别是排除正上,左上,右上有国王的情况(注意只能左移,右移会损失精度)
                if(state[j]&state[p])continue;
                if(state[j]&(state[p]<<1))continue;
                if((state[j]<<1)&state[p])continue;
                for(int s=0;s<=k;s++){//枚举当前行之前已经用了多少个国王了 
                //注:这里s的取值非常非常重要!!!!直接影响到dp数组的意义 
                //如果s的取值从0开始,那表示的是这一行和之前一起放king[j]+s个国王的方案数
                //如果从1开始,表示的是到了这一行正好放king[j]+s个国王,以前够了king[j]+s个棋子的方案没有记录在内 
                    if(s+king[j]<=k)
                        dp[i][j][king[j]+s]+=dp[i-1][p][s];
                } 
            }
        }
        //如果前面s取值从1开始,还要枚举行数i从1到n 
    for(int i=1;i<=res;i++){//记录答案 
        ans+=dp[n][i][k];
    }
    //printf("%I64d",ans);
    cout<<ans;
    return 0;
}
原文地址:https://www.cnblogs.com/qingjiuling/p/11537329.html