【洛谷 P1896】[SCOI2005]互不侵犯(状压dp)

题目链接
题意:在N×N的棋盘里面放K个国王,使他们互不攻击,共有多少种摆放方案。国王能攻击到它上下左右,以及左上左下右上右下八个方向上附近的各一个格子,共8个格子。
这是道状压\(DP\)好题啊。。
定义状态:一个二进制数某一位为\(1\)表示该位放了国王,反之亦然。
\(f[i][j][k]\)表示,前\(i\)行,已经放了\(j\)个国王,并且第\(i\)的状态为\(k\)时的方案数。
直接枚举所有状态显然不可行,于是可以先预处理去所有相邻两格不矛盾的状态,也就是每一行可能出现的状态。
显然,当上一行的状态与该行的状态不矛盾时,状态能转移。
所以,枚举这一行的状态和上一行的状态转移就行了,边界第一行所有状态的方案数都为\(1\)
怎么判断矛不矛盾呢?
把这一行的状态和上一行的状态进行按位与运算就能判断是否存在上下矛盾。
但题目要求\(2\)个国王不能有公共顶点,把这行的状态左移一位再按位与,然后右移一位再按位与就行了。
当三次与运算的结果都是\(0\)时,状态能转移。

#include <cstdio>
#define Open(s) freopen(s".in","r",stdin);freopen(s".out","w",stdout);
#define Close fclose(stdin);fclose(stdout);
const int MAXN = 12;
int n, k;
int vis[MAXN][MAXN];
int s[1024], p[1024];
void dfs(int now, int S, int fi){   //dfs求出一行所有可能的状态,now是当前到第几位了,S是当前状态,fi是已经放了几个国王了
    if(now > n){
      s[++s[0]] = S; p[s[0]] = fi;
      return;
    }
    dfs(now + 1, S, fi);               //不放
    if(now == 1 || !(S & (1 << (now - 2)))) dfs(now + 1, S | (1 << (now - 1)), fi + 1);   //放
}
long long f[MAXN][MAXN * MAXN][1026];
long long ans;
int main(){
  scanf("%d%d", &n, &k);
  dfs(1, 0, 0);
  for(int i = 1; i <= s[0]; ++i)     //边界
     f[1][p[i]][i] = 1;
  for(int i = 2; i <= n; ++i)
     for(int j = 1; j <= s[0]; ++j)      //上一行状态
        for(int o = 1; o <= s[0]; ++o){     //该行状态
           if((s[j] & s[o]) || ((s[j] << 1) & s[o]) || ((s[o] << 1) & s[j])) continue;  //能转移
             for(int l = p[o]; l <= k; ++l)           //转移
                f[i][l][o] += f[i - 1][l - p[o]][j];
        }
  for(int i = 1; i <= s[0]; ++i) ans += f[n][k][i];
  printf("%lld\n", ans);
  return 0;
}

posted @ 2018-09-07 19:57  Qihoo360  阅读(193)  评论(0编辑  收藏  举报
You're powerful!