BZOJ2669: [cqoi2012]局部极小值

Description

有一个nm列的整数矩阵,其中1到nm之间的每个整数恰好出现一次。如果一个格子比所有相邻格子(相邻是指有公共边或公共顶点)都小,我们说这个格子是局部极小值。
给出所有局部极小值的位置,你的任务是判断有多少个可能的矩阵。

Input

输入第一行包含两个整数nm(1<=n<=4, 1<=m<=7),即行数和列数。以下n行每行m个字符,其中“X”表示局部极小值,“.”表示非局部极小值。
 

Output

输出仅一行,为可能的矩阵总数除以12345678的余数。

Sample Input

3 2
X.
..
.X

Sample Output

60

HINT

Source

状压DP+容斥
考虑局部极小值不超过8个 所以状压
把1-n*m一个一个填进去
预处理状态为j的能填的格子数f[j]
DP即可
但是这样只满足给定格子是局部极小值,不保证其他不是局部极小值
暴力dfs回溯容斥即可
复杂度O(能过)
#include <bits/stdc++.h>

using namespace std;

const int mod = 12345678;

int dx[9] = { 1, 1, 0, -1, -1, -1, 0, 1, 0 };
int dy[9] = { 0, -1, -1, -1, 0, 1, 1, 1, 0 };

char mp[10][10];

int n, m, ans, cnt, dp[29][256], f[256];

pair < int, int > st[10];

bool vis[10][10];

inline void upd(int &x, int y) { x += y; if( x >= mod ) x -= mod; if( x < 0 ) x += mod; }

inline int getdp()
{
	memset( f, 0, sizeof( f ) );
	memset( dp, 0, sizeof( dp ) );
	dp[ 0 ][ 0 ] = 1;
	cnt = 0;
	for( int i = 1 ; i <= n ; i++ )
		for( int j = 1 ; j <= m ; j++ )
			if( mp[ i ][ j ] == 'X' )
				st[ ++cnt ] = make_pair( i, j );
	for( int i = 0 ; i < ( 1 << cnt ) ; i++ )
	{
		memset( vis, 0, sizeof( vis ) );
		for( int j = 1 ; j <= cnt ; j++ ) if( !( ( i >> j - 1 ) & 1 ) )
			for( int k = 0 ; k < 9 ; k++ )
			{
				int nx = st[ j ].first + dx[ k ], ny = st[ j ].second + dy[ k ];
				if( !nx || !ny || nx > n || ny > m ) continue;
				vis[ nx ][ ny ] = 1;
			}
		for( int j = 1 ; j <= n ; j++ )
			for( int k = 1 ; k <= m ; k++ )
				if( !vis[ j ][ k ] )
					f[ i ]++;
	}
	for( int i = 1 ; i <= n * m ; i++ )
		for( int j = 0 ; j < ( 1 << cnt ) ; j++ )
		{
			dp[ i ][ j ] = 1ll * dp[ i - 1 ][ j ] * max( 0, f[ j ] - i + 1 ) % mod;
			for( int k = 1 ; k <= cnt ; k++ )
				if( ( j >> k - 1 ) & 1)
					upd( dp[ i ][ j ], dp[ i - 1 ][ j ^ ( 1 << k - 1 ) ] );
		}
	return dp[ n * m ][ ( 1 << cnt ) - 1 ];
}

inline void dfs(int x, int y, int flag)
{
	if( y == m + 1 ) { dfs( x + 1, 1, flag ); return ; }
	if( x == n + 1 ) { upd( ans, getdp() * flag ); return ; }
	dfs( x, y + 1, flag );
	if( mp[ x ][ y ] == '.')
	{
		for( int i = 0 ; i < 8 ; i++ )
		{
			int nx = x + dx[ i ], ny = y + dy[ i ];
			if( !nx || !ny || nx > n || ny > m ) continue;
			if( mp[ nx ][ ny ] == 'X' ) return ;
		}
		mp[ x ][ y ] = 'X';
		dfs( x, y + 1, -flag );
		mp[ x ][ y ] = '.';
	}
}

int main()
{
	scanf( "%d%d", &n, &m );
	for( int i = 1; i <= n ; i++ ) scanf( "%s", mp[ i ] + 1 );
	for( int i = 1; i <= n ; i++ )
		for( int j = 1 ; j <= m ; j++ )
			if( mp[ i ][ j ] == 'X' )
				for( int k = 0 ; k < 8 ; k++ )
				{
					int nx = i + dx[ k ], ny = j + dy[ k ];
					if( !nx || !ny || nx > n || ny > m ) continue;
					if( mp[ nx ][ ny ] == 'X' ) return printf( "0\n" ), 0;
				}
	dfs( 1, 1, 1 );
	return printf( "%d\n", ans ), 0;
}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值