[BZOJ2669][CQOI2012]局部极小值
题目描述
有一个\(n\)行\(m\)列的整数矩阵,其中\(1\)到\(n*m\)之间的每个整数恰好出现一次。如果一个格子比所有相邻格子(相邻是指有公共边或公共顶点)都小,我们说这个格子是局部极小值。
给出所有局部极小值的位置,你的任务是判断有多少个可能的矩阵。
Input
输入第一行包含两个整数\(n\)和\(m\)(\(1<=n<=4, 1<=m<=7\)),即行数和列数。以下\(n\)行每行\(m\)个字符,其中“\(X\)”表示局部极小值,“\(.\)”表示非局部极小值。
Output
输出仅一行,为可能的矩阵总数除以\(12345678\)的余数。
Sample Input
3 2
X.
..
.X
Sample Output
60
首先,我们发现矩阵的大小很小,而局部最小值的位置也很少,最多只有\(8\)个最小值。
\[\begin{matrix}
X & . & X &.&X&.&X \\
. & . & . &.&.&.&. \\
X & . & X &.&X&.&X \\
. & . & . &.&.&.&. \\
\end{matrix}
\]
于是我们可以考虑爆搜,搜出局部最小值的位置,然后在对于每种局部最小值的方案计算方案数。
对于每种局部最小值已知的情况,我们定义\(dp[i][j]\)表示填到第\(i\)个数,局部最小值被填的状态为\(j\)的方案数。
我们考虑计算出没种状态的随便填的位置为\(Sum_i\),则对于当前\(dp[i][j]\)有两种抉择。
1.随便填,即不对\(j\)造成影响,\(dp[i][j]+=dp[i-1][j]*max(Sum_j-i+1,0)\)。
2.填到局部最小值上,即\(dp[i][j|1<<k]+=dp[i][j],k\notin i\)。
dp[0][0] = 1;
rep(i, 1, n * m) ret(j, 0, 1 << tot) {
dp[i][j] += (dp[i - 1][j] * max(0ll, Sum[j] - i + 1) ) % mod;
rep(k, 1, tot)if (!(j & 1 << k - 1))dp[i][j | (1 << k - 1)] += dp[i - 1][j];
}
当前状态的\(ans\)即为\(dp[n*m][(1<<tot)-1]\),\(tot\)为局部最小值的数量。
而\(Sum_j\)即\(n*m-\)当前状态所有局部最小值以及其周围\(8\)个格子的数量。
由于,一个矩阵可能重复被算多次,所以我们要容斥,根据添加的格子的数量进行容斥。
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define reg register
#define clr(a,b) memset(a,b,sizeof a)
#define Mod(x) (x>=mod)&&(x-=mod)
#define abs(a) ((a)<0?-(a):(a))
#define debug(x) cerr<<#x<<"="<<x<<endl;
#define debug2(x,y) cerr<<#x<<"="<<x<<" "<<#y<<"="<<y<<endl;
#define debug3(x,y,z) cerr<<#x<<"="<<x<<" "<<#y<<"="<<y<<" "<<#z<<"="<<z<<endl;
#define rep(a,b,c) for(reg int a=(b),a##_end_=(c); a<=a##_end_; ++a)
#define ret(a,b,c) for(reg int a=(b),a##_end_=(c); a<a##_end_; ++a)
#define drep(a,b,c) for(reg int a=(b),a##_end_=(c); a>=a##_end_; --a)
#define erep(i,G,x) for(int i=(G).Head[x]; i; i=(G).Nxt[i])
#pragma GCC optimize(2)
#pragma GCC optimize(3)
#pragma GCC optimize(3,"Ofast","inline")
inline int Read(void) {
int res = 0, f = 1;
char c;
while (c = getchar(), c < 48 || c > 57)if (c == '-')f = 0;
do res = (res << 3) + (res << 1) + (c ^ 48);
while (c = getchar(), c >= 48 && c <= 57);
return f ? res : -res;
}
template<class T>inline bool Min(T &a, T const&b) {return a > b ? a = b, 1 : 0;}
template<class T>inline bool Max(T &a, T const&b) {return a < b ? a = b, 1 : 0;}
const int N = 3e2 + 5, M = 1e5 + 5, K = 10, mod = 12345678;
const int dx[8] = {1, 0, -1, 1, -1, 1, 0, -1};
const int dy[8] = { -1, -1, -1, 0, 0, 1, 1, 1};
bool MOP1;
int n, m, A[9][9], vis[9][9], us[9][9], dp[30][1 << 9], Sum[1 << 9], Ans;
char S[15];
inline int X(int x) {return (x + m - 1) / m;}
inline int Y(int x) {return x % m ? x % m : m;}
struct node {
int x, y;
} B[15];
void dfs(int pos) {
if (pos == n * m + 1) {
int tot = 0;
rep(i, 1, n)rep(j, 1, m)if (vis[i][j])B[++tot] = (node) {i, j};
clr(dp, 0);
ret(i, 0, 1 << tot) {
clr(us, 0);
int res = 0;
rep(j, 1, tot)if (!(i & 1 << j - 1)) {
int x = B[j].x, y = B[j].y;
if (!us[x][y])us[x][y] = 1, res++;
rep(k, 0, 7) {
int Dx = B[j].x + dx[k], Dy = B[j].y + dy[k];
if (Dx < 1 || Dy < 1 || Dx > n || Dy > m)continue;
if (!us[Dx][Dy])us[Dx][Dy] = 1, res++;
}
}
Sum[i] = n * m - res;
}
dp[0][0] = 1;
rep(i, 1, n * m) ret(j, 0, 1 << tot) {
dp[i][j] += (dp[i - 1][j] * max(0ll, Sum[j] - i + 1) ) % mod, Mod(dp[i][j]);
rep(k, 1, tot)if (!(j & 1 << k - 1))dp[i][j | (1 << k - 1)] += dp[i - 1][j], Mod(dp[i][j | (1 << k - 1)]);
}
int op = tot;
rep(i, 1, n)rep(j, 1, m)if (A[i][j])op--;
if (op & 1) Ans += dp[n * m][(1 << tot) - 1], Mod(Ans);
else Ans += mod - dp[n * m][(1 << tot) - 1], Mod(Ans);
return;
}
int x = X(pos), y = Y(pos);
if (A[x][y]) {
dfs(pos + 1);
return;
}
int flag = 1;
rep(i, 0, 7) {
int Dx = x + dx[i], Dy = y + dy[i];
if (vis[Dx][Dy])flag = 0;
}
if (flag) {
vis[x][y] = 1;
dfs(pos + 1);
vis[x][y] = 0;
}
dfs(pos + 1);
}
bool MOP2;
void _main(void) {
n = Read(), m = Read();
rep(i, 1, n) {
scanf("%s", S);
ret(j, 0, m)vis[i][j + 1] = A[i][j + 1] = (S[j] == 'X');
}
Ans = 0;
dfs(1);
printf("%lld\n", mod - Ans);
}
signed main() {
_main();
return 0;
}