其他分享
首页 > 其他分享> > 【容斥原理+状压DP+DFS】[CQOI2012]-局部极小值

【容斥原理+状压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_,)ノ

*/

标签:ch,int,局部,状压,容斥,DFS,long,极小值,dp
来源: https://www.cnblogs.com/peace0218/p/15434777.html