BZOJ1087:[SCOI2005]互不侵犯——题解

http://www.lydsy.com/JudgeOnline/problem.php?id=1087

Description

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

Input

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

Output

  方案数。

Sample Input

3 2

Sample Output

16

——————————————————————————————

n很小,暴力太麻烦,考虑状压。

设f[i][j][k]表示前i行放j个国王且第i行排成k情况的时候的情况数有多少。

g[i]表示一行国王排成i情况的时候有几个国王。

转移的时候显然是f[i][j][k]+=f[i-1][j-g[k]][l]

其中保证l合法,并且j最小值为g[k]+g[l]。

于是得到算法构架:

枚举i,枚举k,判断k的合法性,枚举l,判断l的合法性,枚举j,计算。

Q1:g怎么算?

A1:求g[i],我们可以通过将i右移,然后判断i最后一位为0还为1,所以答案为:g[i]=g[i>>1]+(i&1);

Q2:如何判断状态合法?

A2:我们判断相邻行i和j状态之间是否合法,首先判断i和j本身是否合法——通过将本身左移,再和原状态&一下,如果不为0就一定撞上了。

再考虑i和j,同样的思路,将j左/右移和i&(当然反过来也可以),如果不为0就一定撞上了。

#include<cstdio>
#include<iostream>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
typedef long long ll;
const int N=512;
const int INF=2147483647;
inline int read(){
    int X=0,w=0;char ch=0;
    while(!isdigit(ch)){w|=ch=='-';ch=getchar();}
    while(isdigit(ch))X=(X<<3)+(X<<1)+(ch^48),ch=getchar();
    return w?-X:X;
}
int g[N];
ll ans,f[10][100][N];
int main(){
    int n,m;
    scanf("%d%d",&n,&m);
    if(m>25||m>=n*n)puts("0");
    else{
    int t=1<<n;f[0][0][0]=1;
    for(int i=1;i<t;i++)g[i]=g[i>>1]+(i&1);
    for(int i=1;i<=n;i++){
        for(int j=0;j<t;j++){
        if(g[j]<=m&&!(j&j>>1)){
            for(int k=0;k<t;k++){
            if(g[k]<=m&&!(k&k>>1)&&!(k&j)&&!(j&k>>1)&&!(j&k<<1)){
                for(int l=g[j]+g[k];l<=m;l++){
                f[i][l][j]+=f[i-1][l-g[j]][k];
                }
            }
            }
        }
        }
    }
    for(int i=0;i<t;i++)ans+=f[n][m][i];
    printf("%lld\n",ans);
    }
    return 0;
}

 

posted @ 2018-01-08 19:28  luyouqi233  阅读(174)  评论(0编辑  收藏  举报