[SCOI2005] 互不侵犯

 洛谷题号:P1896

出处: 2005年四川省选

主要算法:状态压缩dp

难度:4.4

思路分析:

       其实这道题一种很简单的解法是搜索+打表,但是这样很赖皮。这里给出一种状压DP的解法。

  很显然利用普通的DP无法解决了,因为针对点来转移是很难的。但看到N<10很容易想到以行来状压。我们的状压的过程要记录的应该有三个要素,一个是目前说到达的行数,作为一个进程;一个是当前这一行的状态;还有目前已经用了k个骑士,作为约束条件,也确定用来转移的状态——因为如果只有前两个约束条件,状态对应的值是不确定的。

  因此我们可以设置:f[i][j][s]表示第i的状态为j 并且总共用掉了(包括前i行)s个骑士的方案数。注意这里的j对应的是一个状态,也就是一个二进制数字。

  下面来考虑如何转移:既然要求i,肯定会考虑用i-1来转移。那么上一行与这一行有什么关系呢?首先,我们需要枚举上一行与当前这一行的所有可能状态。而对于一行的状态是可以预处理的,并且舍去一些不可能的情况(例如两个骑士靠在一起的)。我们可以先用一遍O(2^n)的DFS求解出对于一行的所有可能状态,然后枚举上一行与当前这一行的状态。在两种状态可以并存的情况下累积方案数。也就是f[i][j][s] += f[i-1][k][s-num[j]]; 其中k表示上一行的状态,num[j]表示目前这一行将要占用num[j]个骑士,也就是状态j对应的1的个数。

  现在还剩最后一个问题,如何判断上一行的状态与这一行的状态能否并存?题目告诉我们一个骑士周围的8个格子不能有别的骑士,而左右在DFS预处理的时候就已经舍掉了,因此要判的有正下方,左下方,右下方。对于正下方,我们直接做j & k,如果其不为0,这意味着至少有一位是上下都有的,于是舍去;如果j & (k<<1),则意味着左下角有骑士——因为上方的状态向左移动一格后有重叠,则意味着原先的左下角就有骑士了; 那么右下角也一样,j & (k >> 1)

   另外,第一行的可行状态初始化为1。这还是很容易想到的,不然答案全是0.

代码注意点:

  long long

  值得注意的是,我们在dfs中预处理出的sta[j],而f数组中用的是j,所以初始化的时候不是初始化sta[j],而是j。同时,dfs的边界条件也要注意

/*By QiXingzhi*/
#include <cstdio>
#define  N  (4010)
#define  r  read()
#define  INF   (0x3f3f3f3f)
#define  Max(a,b)  (((a)>(b)) ? (a) : (b))
#define  Min(a,b)  (((a)<(b)) ? (a) : (b))
typedef long long ll;
#define  int ll
using namespace std;
inline int read(){
    int x = 0; int w = 1; register int c = getchar();
    while(c ^ '-' && (c < '0' || c > '9')) c = getchar();
    if(c == '-') w = -1, c = getchar();
    while(c >= '0' && c <= '9') x = (x << 3) +(x << 1) + c - '0', c = getchar();
    return x * w;
}
int n,K,tot,ans;
int sta[N],num[N],f[12][N][144];
void Dfs(int x, int cur, int sum){
    if(x >= n){
        ++tot;
        sta[tot] = cur;
        num[tot] = sum;
        f[1][tot][sum] = 1;
        return;
    }
    Dfs(x+1,cur,sum);
    Dfs(x+2,cur+(1<<x),sum+1);
}
#undef int
int main(){
#define  int ll
    n=r,K=r;
    Dfs(0,0,0);
    for(int i = 2; i <= n; ++i){
        for(int j = 1; j <= tot; ++j){
            for(int k = 1; k <= tot; ++k){
                if(sta[j] & sta[k]) continue;
                if(sta[j] & (sta[k] << 1)) continue;
                if(sta[j] & (sta[k] >> 1)) continue;
                for(int s = num[j]; s <= K; ++s){
                    f[i][j][s] += f[i-1][k][s-num[j]];
                }
            }
        }
    }
    for(int i = 1; i <= tot; ++i) ans += f[n][i][K];
    printf("%lld",ans);
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/qixingzhi/p/9264853.html