【容斥原理+状压DP+DFS】[CQOI2012]-局部极小值

嘤嘤嘤好难,写的乱七八糟

题目链接(https://www.luogu.com.cn/problem/P3160)

题目描述

有一个nm列的整数矩阵,其中1nm之间的每个整数恰好出现一次。如果一个格子比所有相邻格子(相邻是指有公共边或公共顶点)都小,我们说这个格子是局部极小值。

给出所有局部极小值的位置,你的任务是判断有多少个可能的矩阵。

输入描述:

输入第一行包含两个整数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 ~ nm进行动态规划,先预处理掉不合法的情况。

每个位置有两种操作:

(1)填,枚举state表示每一个已经填好的极小值,

dp[i][state] +=k|(1<<k)&state==0 dp[i1][state(1<<k)]s(1<<k)表示若s(1<<k)没有填K位置的局部极小值,则i的种类数是dp[i1][state(1<<k)]

(2)不填,枚举每个点,有新的填法,设为sum,则dp[i][state] += dp[i1][state](sumi+1),枚举复杂度会爆炸,进行优化,预处理出每种状态i对应的新的可填点数量,设为pre[i]

得答案为dp[nm][(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_,)ノ

*/
posted @   TomiokapEace  阅读(45)  评论(1编辑  收藏  举报
编辑推荐:
· 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代理技术深度解析与实战指南
点击右上角即可分享
微信分享提示