[SCOI2005]互不侵犯

Posted repulser

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[SCOI2005]互不侵犯相关的知识,希望对你有一定的参考价值。

[SCOI2005]互不侵犯

状压DP练习题

我们先审个题:

  首先注意这道题的数据范围

$1leq N leq 9? ,?0 leq Kleq N^2$

是不是真的很小啊,所以我们考虑用状压DP或爆搜的办法解。

解法:

  这道题求方案数,那么可以很自然的想到用dp或者搜索,然而对于本题而言,搜索需要记录的信息和状态太多,会T到天上去,所以只有采用状压DP来解决。
   注意到n非常小,而且状态需要记录的信息非常多,所以考虑状态压缩dp。在这道题里,当前行的状态和上一行的状态有着密切联系,而且答案和k有关,所以我们设计状态f[i][j][k]为前i行已经放了j个国王并且第i行的状态为k(二进制)的方案数,那么$f[i][j][k] = sum f[i - 1][j - num[k]][l]$,其中num数组记录着一行为状态k的放的国王的数目,l为上一行符合要求的状态.
现在的问题就是如何判断状态i是否符合要求,这不仅与i本身有关,还和它上一行的状态j有关,利用左移,右移,&就可以判断了,它的原理是什么呢?我们把每个状态抽象成1个二进制数,它的第i位表示第i列放不放国王,利用&的性质,可以判断上下两行是否冲突,那么如何判断当前行是否冲突呢?将i左移1位再与i进行&操作即可。

CODE:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>

#define N 1100
#define M 15
#define ll long long

using namespace std;

int n,k,num[N];
ll f[M][N][N],ans;
bool flag[N];

int main() {
    scanf("%d%d",&n,&k);
    for(int i = 0 ; i < (1<<n) ; i++) {
        if(!(i & (i<<1))) {
            flag[i] = 1;
            int t = i;
            while(t) {
                num[i] += (t & 1);
                t >>= 1;
            }
            f[1][num[i]][i] = 1;
        }
    }
    for(int i = 2 ; i <= n ; i++)
        for(int j = 0 ; j <= k ; j++)
            for(int l = 0 ; l < (1<<n) ; l++) {
                if(!flag[l]) continue;
                if(num[l] > j) continue;
                for(int last = 0 ; last < (1<<n) ; last++) {
                    if(!flag[last]) continue;
                    if((last & l) || (l<<1)&last || (l>>1)&last) continue;
                    f[i][j][l] += f[i - 1][j - num[l]][last];
                }
            }
    for(int i = 0 ; i < (1<<n) ; i++)
        ans += f[n][k][i];
    printf("%lld",ans);
    return 0;
}

以上是关于[SCOI2005]互不侵犯的主要内容,如果未能解决你的问题,请参考以下文章

[SCOI2005]互不侵犯King

Luogu P1896 [SCOI2005]互不侵犯

[SCOI2005]互不侵犯

bzoj1087 [SCOI2005]互不侵犯King

bzoj1087SCOI2005互不侵犯King

题解互不侵犯 SCOI 2005 BZOJ 1087 插头dp