【SCOI2005】【BZOJ1087】互不侵犯King(状压dp)

problem

  • 在N×N的棋盘里面放K个国王
  • 每个国王会攻击它周围的一圈共8个格子
  • 使他们互不攻击,共有多少种摆放方案
  • N <= 9

solution

  • 用01串表示某一行放置的情况
  • 首先枚举当前做到第几行,以及当前一共放了几颗棋子。
  • 于是状态f[i][j][k]表示到第i行,一共放j个棋子(包括这之前的),且第i行的状态是k的方案数。
  • 再考虑转移。这一行肯定是由上一行的状态转移过来的,那么我们可以再枚举上一行的状态。
  • 很自然的,发现这会超时。每次枚举一种状态就需要2^9,两重循环已经快爆掉了!我们可以发现一件事情。比如n=5,我们每次枚举到的11111,11011,10111,01011这些状态都是无效的。那么我们可以先预处理一下对于每一行的所有可行的状态(就是不能有连续的1)。
  • 这样的效率仍然不高——我们还可以对于每种可行的状态i,j,预处理i和j是否能够相邻,这样我们在DP的时候,就可以O(1)来转移了。(这里也可以不预处理,每次直接判断ij能否相邻也可。)

最后,记得开long long。

codes

#include<iostream>
using namespace std;
const int maxn = 512;
typedef long long LL;
int c1[maxn], cnt[maxn], c2[maxn][maxn];
LL ans, f[10][100][maxn];
int main(){
    int n, m;
    cin>>n>>m;
    int all = (1<<n)-1;
    for(int i = 0; i <= all; i++){
        if((i&(i>>1))==0){
            c1[i] = 1;
            for(int x = i; x; x >>= 1) cnt[i]+= (x&1);
        }
    }
    for(int i = 0; i <= all; i++)if(c1[i])f[1][cnt[i]][i] = 1;
    for(int i = 1; i < n; i++){
        for(int j = 0; j <= all; j++)if(c1[j]){
            for(int k = 0; k <= all; k++)if(c1[k]){
                if(((j&k)==0)&&((j&(k>>1))==0)&&((j&(k<<1))==0)){
                    for(int p = cnt[j]; p+cnt[k]<=m; p++)
                        f[i+1][p+cnt[k]][k] += f[i][p][j];
                }
            }
        }
    }
    for(int i = 0; i <= all; i++)ans += f[n][m][i];
    cout<<ans<<"\n";
    return 0;
}
posted @ 2018-05-24 21:12  gwj1139177410  阅读(111)  评论(0编辑  收藏  举报
选择