「luogu 1896」 互不侵犯 - 轮廓线 dp

题目描述

给定 \(N,K\) , 问 \(N \times N\) 的棋盘中放 \(K\) 个国王有多少种方案

\(N\le 9,K\le N\times N\)

链接

bzoj 1087
luogu P1896

题解

考虑轮廓线 dp

我们枚举每个点,考虑一个轮廓线的转移

轮廓线dp.png

\(f_{i,j,k,t}\) 表示 第 \(i\) 行 第 \(j\) 列 放了 \(k\) 个棋子 状态是 \(t\) 时有多少种方案

然后发现 \(f{i,j}\) 只和前一个有关, 于是可以滚动数组, \(t\) 是一个二进制数, 是 \(1\) 说明有棋子, 是 \(0\) 说明没有棋子, 如上图所示, 第 \(0\) 位就是当前的位置, 第 \(1\) 位是当前左边的, 以此类推, 左上方的就是第 \(n\) 位, 上方的就是 \(n-1\) 位, 右上方的就是 \(n-2\)

考虑下一个状态, 就是向右移动了一格, 就是把当前第 \(n\) 位扔掉, 第 \(n\) 位变成第 \(n-1\) 位, 第 \(n-1\) 位变成 \(n-2\) 位, 以此类推. 第 \(0\) 位变成 \(0\)\(1\)

最后判断是否能放, 就是看左,左上,上,右上是否有棋子,如果没有就可以放,当然,也可以不放

#include <cstdio>
#include <cstring>
typedef long long ll;

const int MAXS=1025;
const int MAXK=82;

int N,K,cur;
ll res,f[2][MAXS][MAXK];

inline bool check(int i,int j,int s){
    if (i!=1 && j!=1 && (s&(1<<N))) return 0;
    if (i!=1 && (s&(1<<N-1))) return 0;
    if (i!=1 && j!=N && (s&(1<<N-2))) return 0;
    if (j!=1 && (s&1)) return 0;
    return 1;
}

int main(){
    scanf("%d%d",&N,&K);
    f[0][0][0]=1;
    for (int i=1;i<=N;++i)
        for (int j=1;j<=N;++j){
            cur^=1;
            memset(f[cur],0,sizeof(f[cur]));
            for (int s=0;s<(1<<N+1);++s){
                int now=(s&((1<<N)-1))<<1;
                for (int p=0;p<=K;++p){
                    f[cur][now][p]+=f[cur^1][s][p];
                    if (p<K && check(i,j,s)) f[cur][now^1][p+1]+=f[cur^1][s][p];
                }
            }
        }
    for (int i=0;i<(1<<N+1);++i) res+=f[cur][i][K];
    printf("%lld\n",res);
    return 0;
}
posted @ 2018-09-24 15:51 xay5421 阅读(...) 评论(...) 编辑 收藏