题解 P1896 【[SCOI2005]互不侵犯】

题目链接

Solution [SCOI2005]互不侵犯

题目大意:给定两个数\(n\),\(k\),求在\(n^2\)大小的棋盘内放置\(k\)个国王的方案数

分析一下题目:\(n\)很小啊,\(1 \leq n \leq 9\),而且国王的攻击范围也很有特点,蒟蒻马上就想到了状压\(dp\)

稍加分析即可列出\(dp\)的状态,以\(f[i][s][k]\)来表示前\(i\)行,第\(i\)行的状态为\(s\),到第\(i\)行共计有\(k\)个国王的方案总数

状态容易想,转移方程呢?

\(f[i][s][k] = \sum f[i - 1][z][k - cnt(s)]\),我们枚举由上一行哪个状态\(z\)转移而来,\(cnt(x)\)表示二进制下数\(x\)\(1\)的总数(也就是状态\(s\)的国王个数)

最后一点,边界:

\(f[1][s][cnt(s)] = 1\),这个应该很好理解吧

最后为了加速,我们可以先预处理出可能的状态(比如一行内两个国王紧挨着肯定是不行的)

最后奉上蒟蒻的代码:

Note:代码里的s是状态的编号

#include <cstdio>
using namespace std;
const int maxn = 10;
const int maxs = 1 << 10;//方案总数
inline int cnt(int x){//统计二进制下1的数量
    int ret = 0;
    while(x){
        if(x & 1)ret++;
        x >>= 1;
    }
    return ret;
}
inline bool check(int x){//判断方案x是否可行(用于预处理)
    if(x & (x << 1))return false;
    if(x & (x >> 1))return false;
    return true;
}
int status[maxs],tot;//预处理出的方案,tot为方案总数
long long f[maxn][maxs][maxn * maxn];//dp数组
int N,K,full;
int main(){
    scanf("%d %d",&N,&K);
    full = (1 << N) - 1;//full全集
    for(int i = 0;i <= full;i++)//预处理
        if(check(i))
            status[++tot] = i;
    for(int i = 1;i <= tot;i++)//边界条件
        f[1][i][cnt(status[i])] = 1;
    for(int i = 2;i <= N;i++)//dp
        for(int s = 1;s <= tot;s++)
            for(int k = cnt(status[s]);k <= K;k++)
                for(int z = 1;z <= tot;z++){//枚举由上一行的状态z转移而来
                    int x = status[s];
                    int y = status[z];
                    if(x & y)continue;//判断当前行状态x与枚举的上一行状态y是否冲突
                    if(x & (y >> 1))continue;
                    if(x & (y << 1))continue;
                    f[i][s][k] += f[i - 1][z][k - cnt(status[s])];
                }
    long long ans = 0;
    for(int i = 1;i <= tot;i++)//最后统计全都加起来
        ans += f[N][i][K];
    printf("%lld\n",ans);
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/colazcy/p/11514721.html