【容斥原理+状压DP+DFS】[CQOI2012]-局部极小值
嘤嘤嘤好难,写的乱七八糟
题目链接(https://www.luogu.com.cn/problem/P3160)
题目描述
有一个行列的整数矩阵,其中到之间的每个整数恰好出现一次。如果一个格子比所有相邻格子(相邻是指有公共边或公共顶点)都小,我们说这个格子是局部极小值。
给出所有局部极小值的位置,你的任务是判断有多少个可能的矩阵。
输入描述:
输入第一行包含两个整数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。
思路
题意言简意赅,没有想到可以用状压处理局部极小值的个数,做法参考了博客
由题意很容易就能知道肯定不能相邻或者对角相邻,一个相邻个空格一不能在存在,数据范围很小,可知该图最多只能有个局部极小值,联想到状压。
用表示每个极小值的点是否已经填好的状态,枚举 ~ 进行动态规划,先预处理掉不合法的情况。
每个位置有两种操作:
(1)填,枚举表示每一个已经填好的极小值,
+= (表示若没有填位置的局部极小值,则的种类数是)
(2)不填,枚举每个点,有新的填法,设为,则 += ,枚举复杂度会爆炸,进行优化,预处理出每种状态对应的新的可填点数量,设为
得答案为
最后处理掉可能出现的不合法的位置出现局部极小值的情况(以外还有局部极小值)如果分别去减,由于减的方案数还要像上面那样出来,会产生更多的重复,所以利用容斥,即个点反转-个点反转+个点反转....用实现。
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_,)ノ
*/
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南