P3160 [CQOI2012]局部极小值

[CQOI2012]局部极小值

题目描述

有一个 nm 列的整数矩阵,其中 1n×m 之间的每个整数恰好出现一次。

如果一个格子比所有相邻格子(相邻是指有公共边或公共顶点)都小,我们说这个格子是局部极小值。给出所有局部极小值的位置,你的任务是判断有多少个可能的矩阵。

答案对 12,345,678 取模。

  • 对于 100% 的数据,保证 1n41m7

思路点拨

如果我们只让题目给的局部最小值点满足条件,其余的点随意放,可能导致产生新的局部最小值。所以这不好搞,我们就先简化问题,不考虑其余新的局部最小值情况。

如何求出一个给定地图中,满足条件的方案数呢?我们考虑状态压缩,数据范围十分明显了。但是我们有仔细一想, 228 的状态固然好想,但是过不了。并且具体的,对于我们状态转移有实际上作用的只有那些应该要有的局部最小值点。这样的先在 4×7 的地图中最多只有 8 个。

这样我们只需要状压那些局部最小值点就可以了。整合一下,我们定义 f[i][S] 表示填到 i 个数,S 表示局部最小值的状态压缩。g[S]S 表示局部最小值的状态压缩,这表示这些点选了之后,有那些非局部最小值点可以选。(如果一个非局部最小值点可以先,就是它周围的 8 个格子中的局部最小值点都选了,不然就会不合法)。

转移分两类,一种是选了一个局部最小值点: f[i][S]=f[i1][S(1<<i)]

另一种就是选择了非局部最小值点。假设目前状态 S 中选择了 k 个局部最小值点。那么 f[i][S]=f[i1][S]×(g[S](i1k)) 。在实现中可以给 g[S] 先给 g[S] 加上 k

OK,这样就解决了我们的第一部分,动态规划。这里贴出部分代码,变量名是一样的。

bool vis[MAXN][MAXN];
int dp(){
	int cnt=0;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
			if(a[i][j]=='X')
				add(i,j,cnt);
	memset(f,0,sizeof(f));
	memset(g,0,sizeof(g));
	for(int t=0;t<(1<<cnt);t++){
		memset(vis,0,sizeof(vis));
		for(int i=0;i<cnt;i++)
			if(!(t&(1<<i))){
				vis[p[i+1].x][p[i+1].y]=1;
				for(int j=0;j<8;j++)
					vis[p[i+1].x+X[j]][p[i+1].y+Y[j]]=1;
			}
		for(int i=1;i<=n;i++)
			for(int j=1;j<=m;j++)
				g[t]+=vis[i][j];
		g[t]=n*m-g[t];
	}
	f[0][0]=1;
	for(int i=1;i<=n*m;i++){
		for(int t=0;t<(1<<cnt);t++){
			if(g[t]-i+1>0) f[i][t]=(f[i][t]+f[i-1][t]*(g[t]-i+1))%mod;
			for(int j=0;j<cnt;j++)
				if(t&(1<<j))
					f[i][t]=(f[i][t]+f[i-1][t-(1<<j)])%mod;
		}
	}
	return pow(-1,cnt-tot)*f[n*m][(1<<cnt)-1]+mod;
}

但是这样是不对的,应为我们在除了局部最小值点之外的点乱填的时候,可能导致新的局部最小值点产生。所以我们直接容斥。
画画韦恩图可知这既是一个一般容斥( tot 表示一开始就有的局部最小值点数量 ):

tot(tot+1)+tot+2

具体实现爆搜就可以了,方案不会很多。

最终给出前部代码:

code

#include<bits/stdc++.h>
#define int long long
using namespace std;
inline int read(){
	int x=0,f=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){
		if(ch=='-') f=-f;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9'){
		x=x*10+ch-'0';
		ch=getchar();
	}
	return x*f;
}
const int MAXN=9,mod=12345678;
int n,m;
char a[MAXN][MAXN];
int f[30][1<<MAXN],g[1<<MAXN];
int X[8]={1,-1,0,0,1,1,-1,-1};
int Y[8]={0,0,1,-1,1,-1,1,-1};
struct node{
	int x,y;
}p[MAXN]; 
void add(int x,int y,int &tot){
	++tot;
	p[tot].x=x;
	p[tot].y=y;
}
int check(int x,int y){
	int cnt=0;
	for(int i=0;i<8;i++){
		int xx=x+X[i],yy=y+Y[i];
		if(a[xx][yy]=='X') cnt++;
	}
	return cnt;
} 
bool Error(){
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
			if(a[i][j]=='X'&&check(i,j))
				return 0;
	return 1;
}
int sum[MAXN],tot;
void prepare(){
	sum[0]=1;
	for(int i=1;i<MAXN;i++)
		sum[i]=sum[i-1]*i;
}
int C(int n,int m){
	return sum[n]/(sum[m]*sum[n-m]);
}
bool vis[MAXN][MAXN];
int dp(){
	int cnt=0;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
			if(a[i][j]=='X')
				add(i,j,cnt);
	memset(f,0,sizeof(f));
	memset(g,0,sizeof(g));
	for(int t=0;t<(1<<cnt);t++){
		memset(vis,0,sizeof(vis));
		for(int i=0;i<cnt;i++)
			if(!(t&(1<<i))){
				vis[p[i+1].x][p[i+1].y]=1;
				for(int j=0;j<8;j++)
					vis[p[i+1].x+X[j]][p[i+1].y+Y[j]]=1;
			}
		for(int i=1;i<=n;i++)
			for(int j=1;j<=m;j++)
				g[t]+=vis[i][j];
		g[t]=n*m-g[t];
	}
	f[0][0]=1;
	for(int i=1;i<=n*m;i++){
		for(int t=0;t<(1<<cnt);t++){
			if(g[t]-i+1>0) f[i][t]=(f[i][t]+f[i-1][t]*(g[t]-i+1))%mod;
			for(int j=0;j<cnt;j++)
				if(t&(1<<j))
					f[i][t]=(f[i][t]+f[i-1][t-(1<<j)])%mod;
		}
	}
	return pow(-1,cnt-tot)*f[n*m][(1<<cnt)-1]+mod;
}
int ans;
void dfs(int x,int y){
	if(y==m+1){
		if(x==n) ans=(ans+dp()+mod)%mod;
		else dfs(x+1,1);
	}
	else{
		if(a[x][y]=='X') dfs(x,y+1);
		else{
			dfs(x,y+1);
			if(!check(x,y)){
				a[x][y]='X';
				dfs(x,y+1);
				a[x][y]='.';
			}
		}
	}
}
signed main(){
	cin>>n>>m;
	prepare();
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
			cin>>a[i][j];
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
			if(a[i][j]=='X') tot++;
	if(!Error()) cout<<0;
	else{
		dfs(1,1);
		cout<<ans;
	}
	return 0;
}
posted @   Diavolo-Kuang  阅读(19)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示