首页 > 题解 > bzoj 2669 [cqoi2012]局部极小值

bzoj 2669 [cqoi2012]局部极小值

Description

有一个n行m列的整数矩阵,其中1到nm之间的每个整数恰好出现一次。如果一个格子比所有相邻格子(相邻是指有公共边或公共顶点)都小,我们说这个格子是局部极小值。
给出所有局部极小值的位置,你的任务是判断有多少个可能的矩阵。

Input

输入第一行包含两个整数n和m(1<=n<=4, 1<=m<=7),即行数和列数。以下n行每行m个字符,其中“X”表示局部极小值,“.”表示非局部极小值。

Output

输出仅一行,为可能的矩阵总数除以12345678的余数。

Sample Input

3 2

X.

..

.X

Sample Output

60

题解

转个学姐的。。

这道题的数据范围很小。我们很容易想到状压动归,但是如果我们枚举每一个位置放置什么就一定会存在判断当前数是否已经用过的问题,2^28很显然不能承受。

我们发现局部极小值的点最多8个,所以我们考虑把局部最小值是否已经填过状压成一维,然后考虑从小到大往格子中填数,因为每个数枚举一次所以也就只能填一次。

f[i][j]表示填到数i,局部最小值的填写状态为j。

i数可以填写到的位置可以分成两种,‘x’位置,和非’x’位置。

如果考虑填入‘x’位置,因为是从小到大开始填写,所以直接填就好,因为周围的点要么没填要么填的比这个位置小。我们只需要令j状态中填写的位置k,f[i][j]+=f[i-1][j^(1<<k-1)]

如果考虑填入非‘x’位置,很显然有些位置是不能填的(就是那些未填的’x’位置和他周围的位置),我们可以预处理出每种状态对应的可以填写的位置的个数(即除去那些未填的’x’位置和他周围的位置剩下的位置),但是这些位置中已经填写了(i-1)个数,

所以在递推的时候要减去,即f[i][j]+=f[i-1][j]*max(num[j]-i+1,0)。

但是我们这样算完后发现答案还是不对,为什么呢?因为我们是保证了’x’位置是局部极小值,但是并没有保证’.’的位置不是,局部最小值,所以Ans=多包含至少0个局部极小值-多包含至少1个局部极小值+多包含至少2个局部极小值-…

这个容斥的时候用dfs搜索出那些不是’x’但是有可能成为局部极小值的位置,然后用上面的方式计算一下,在更新最终答案即可。

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
int n, m, tot, ans, vis[30][30], p[30][2], num[1 << 9], f[30][1 << 9];
char s[30][30];
const int mod = 12345678;
int dir[8][2] = {{1, 0}, {1, -1}, {1, 1}, {0, 1}, {0, -1}, {-1, 0}, {-1, 1}, {-1, -1}};
bool check(int x, int y) { if (x <= n && x >= 1 && y <= m && y >= 1) return 1; return 0; }
int solve() {
    tot = 0;
    for (int i = 1; i <= n; ++i)
        for (int j = 1; j <= m; ++j)
            if (s[i][j] == 'X')
                p[++tot][0] = i, p[tot][1] = j;
    for (int i = 0; i < (1 << tot); ++i) {
        memset(vis, 0, sizeof vis);
        for (int j = 1; j <= tot; ++j)
//          if (i & (1 << j - 1)) {
            if (!((i >> (j - 1)) & 1)) {
                vis[p[j][0]][p[j][1]] = 1;
                for (int k = 0; k < 8; ++k)
                    if (check(p[j][0] + dir[k][0], p[j][1] + dir[k][1]))
                        vis[p[j][0] + dir[k][0]][p[j][1] + dir[k][1]] = 1;
            }
        int ans = 0;
        for (int j = 1; j <= n; ++j)
            for (int k = 1; k <= m; ++k)
                if (vis[j][k]) ans++;
        num[i] = n * m - ans;
    }
    memset(f, 0, sizeof f);
    f[0][0] = 1;
    for (int i = 1; i <= n * m; ++i)
        for (int j = 0; j < (1 << tot); ++j) {
            (f[i][j] += f[i - 1][j] * max(num[j] - i + 1, 0) % mod) %= mod;
            for (int k = 1; k <= tot; ++k)
                if (j & (1 << k - 1))
                    (f[i][j] += f[i - 1][j ^ (1 << k - 1)]) %= mod;
        }
    return f[n * m][(1 << tot) - 1];
}
void dfs(int x, int y, int k) {
    if (y > m) {
        dfs(x + 1, 1, k); return;
    }
    if (x > n) {
        if (k & 1) ans = (ans - solve() + mod) % mod;
        else ans = (ans + solve()) % mod;
        return;
    }
    dfs(x, y + 1, k);
    int f = 1;
    for (int i = 0; i < 8; ++i)
        if (s[x + dir[i][0]][y + dir[i][1]] == 'X') {
            f = 0; break;
        }
    if (f && s[x][y] != 'X')
        s[x][y] = 'X', dfs(x, y + 1, k + 1), s[x][y] = '.';
}
main() {
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; ++i) {
        scanf("%s", s[i] + 1);
        for (int j = 1; j <= m; ++j)
            if (s[i][j] == 'X')
                p[++tot][0] = i, p[tot][1] = j;
    }
    dfs(1, 1, 0);
    printf("%d\n", (ans % mod + mod) % mod);
}