【容斥原理+状压DP+DFS】[CQOI2012]-局部极小值
嘤嘤嘤好难,写的乱七八糟
题目链接(https://www.luogu.com.cn/problem/P3160)
题目描述
有一个\(n\)行\(m\)列的整数矩阵,其中\(1\)到\(nm\)之间的每个整数恰好出现一次。如果一个格子比所有相邻格子(相邻是指有公共边或公共顶点)都小,我们说这个格子是局部极小值。
给出所有局部极小值的位置,你的任务是判断有多少个可能的矩阵。
输入描述:
输入第一行包含两个整数n和m(1 ≤ n ≤ 4, 1 ≤ m ≤ 7),即行数和列数。
以下n行每行m个字符,其中“X”表示局部极小值,“.”表示非局部极小值。
输出描述:
输出仅一行,为可能的矩阵总数除以12345678的余数。
示例1
输入
3 2
X.
..
.X
输出
60
备注:
对于100%的数据,保证 1≤n≤4,1≤m≤7。
思路
题意言简意赅,没有想到可以用状压处理局部极小值的个数,做法参考了博客
由题意很容易就能知道\(X\)肯定不能相邻或者对角相邻,一个\(X\)相邻\(8\)个空格一不能在存在\(X\),数据范围很小,可知该图最多只能有\(8\)个局部极小值,联想到状压。
用\(state\)表示每个极小值的点是否已经填好的状态,枚举\(1\) ~ \(n * m\)进行动态规划,先预处理掉不合法的情况。
每个位置有两种操作:
(1)填,枚举\(state\)表示每一个已经填好的极小值,
\(dp[i][state]\) +=\(\sum\)\(_{k | (1 << k) \& state == 0}\) \(dp[i - 1][state - (1 << k)]\)(\(s - (1 << k)\)表示若\(s - (1 << k)\)没有填\(K\)位置的局部极小值,则\(i\)的种类数是\(dp[i - 1][state - (1 << k)]\))
(2)不填,枚举每个点,有新的填法,设为\(sum\),则\(dp[i][state]\) += \(dp[i - 1][state] * (sum - i + 1)\),枚举复杂度会爆炸,进行优化,预处理出每种状态\(i\)对应的新的可填点数量,设为\(pre[i]\)
得答案为\(dp[n * m][(1 << cnt) - 1]\)
最后处理掉可能出现的不合法的位置出现局部极小值的情况(\(X\)以外还有局部极小值)如果分别去减,由于减的方案数还要像上面那样\(dp\)出来,会产生更多的重复,所以利用容斥,即\(0\)个点反转-\(1\)个点反转+\(2\)个点反转....用\(dfs\)实现。
AC代码
#include <bits/stdc++.h>
#define inf 0x3f3f3f3f
#define llinf 0x3f3f3f3f3f3f3f3f
#define int long long
#define ull unsigned long long
#define PII pair<int,int>
using namespace std;
typedef long long ll;
const int N = 10;
const int mod = 12345678;
// inline int read() {
// int f = 1, x = 0; char ch;
// do { ch = getchar(); if (ch == '-') f = -1; } while (ch<'0' || ch>'9');
// do { x = x * 10 + ch - '0'; ch = getchar(); } while (ch >= '0'&&ch <= '9');
// return x * f;
// }
char a[10];
int n, m, gra[6][10];//描述整个矩阵
int cnt;//记录局部极小值个数
int x[30], y[30];//记录位置
int dx[10] = { -1,-1,-1,0,0,1,1,1,0 };//表示移动方向
int dy[10] = { -1,0,1,-1,1,-1,0,1,0 };
int vis[6][10], dp[29][(1 << 8) + 10];
int pre[1 << 9];//预处理
int fun() {
memset(dp, 0, sizeof(dp));
dp[0][0] = 1;
for (int i = 0; i < (1 << cnt); i++) {//预处理出每个状态i对应的可填点数量
pre[i] = n * m;
memset(vis, 0, sizeof(vis));
for (int j = 0; j < cnt; j++)
if (!(i & (1 << j)))
for (int k = 0; k < 9; k++)
vis[x[j] + dx[k]][y[j] + dy[k]] = 1;
for (int j = 1; j <= n; j++)
for (int k = 1; k <= m; k++)
if (vis[j][k])pre[i]--;
}
for (int i = 1; i <= n * m; i++) {//枚举填哪个数
for (int j = 0; j < (1 << cnt); j++) {//枚举状态
if (pre[j] - i + 1 > 0) {
dp[i][j] += dp[i - 1][j] * (pre[j] - i + 1);
dp[i][j] %= mod;
}
for (int k = 0; k < cnt; k++) {
if ((1 << k) & j) {
dp[i][j] += dp[i - 1][j ^ (1 << k)];
dp[i][j] %= mod;
}
}
}
}
return dp[n * m][(1 << cnt) - 1];
}
int dfs(int fx, int fy) {
if (fy == m + 1) {
fx++;
fy = 1;
}
if (fx == n + 1) return fun();
int ans = dfs(fx, fy + 1);
for (int i = 0; i < 9; i++) {
if (gra[fx + dx[i]][fy + dy[i]]) return ans;
} //若没有返回值,则说明此处有可能成为不合法的局部极小值,需要继续dfs
x[cnt] = fx;
y[cnt] = fy;
cnt++;
gra[fx][fy] = 1;
ans -= dfs(fx, fy + 1);
ans = (ans + mod) % mod;
gra[fx][fy] = 0; cnt--;//回溯
return ans;
}
void solve() {
for (int i = 1; i <= n; i++) {
scanf("%s", a + 1);
for (int j = 1; j <= m; j++)
if (a[j] == 'X') {
gra[i][j] = 1;
x[cnt] = i;
y[cnt] = j;
cnt++;
}
}//初始化
for (int i = 0; i < cnt; i++)
for (int j = 0; j < i; j++)
if (abs(x[i] - x[j]) < 2 && abs(y[i] - y[j]) < 2) {
printf("0\n");
return;
}
//判断输入是否合法
if (!cnt) {
printf("0\n");
return;
}
printf("%d", dfs(1, 1));
return;
}
signed main() {
// ios::sync_with_stdio(false);
// cin.tie(0); cout.tie(0);
while (~scanf("%d%d", &n, &m)) {
solve();
}
return 0;
}
/*
i raised a cute kitty in my code,
my friend who pass by can touch softly on her head:)
/l、
Meow~(゚、 。7
|、 ~ヽ
じしf_,)ノ
*/