[bzoj2669][DP][DFS][容斥原理]局部极小值

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Rose_max/article/details/82763658

Description

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

Input

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

Output

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

Sample Input

3 2

X.

.X

Sample Output

60

题解

有点奇妙…
首先数据范围极小考虑做一些非法的事情
发现极限数据下最多的合法点只会有八个更加启导我们做非法的事情
从小到大枚举填的数
状压 f [ i ] [ j ] f[i][j] 表示当前填到了第 i i 个数,当前极小值位置是否被填了的状态
分是否填X位置讨论
如果填X位置 显然可以直接填入因为是从小到大枚举的且保证了X位置旁边不会有数
转移 f [ i ] [ j ( 1 &lt; &lt; K ) ] + = f [ i 1 ] [ j ] f[i][j|(1&lt;&lt;K)]+=f[i-1][j]
如果不填X位置,预处理一个 n u m [ j ] num[j] 表示在 j j 状态下除去不能填数的格子其它的格子有多少个
显然已经填了的X格子都可以对 n u m [ j ] num[j] 做出贡献
枚举每一个格子,如果他的八连通中没有X格子 或者没有没有被填的X格子 这个格子也能对num[j]做出贡献
转移 f [ i ] [ j ] + = f [ i 1 ] [ j ] ( n u m [ j ] ( i 1 ) ) f[i][j]+=f[i-1][j]*(num[j]-(i-1))
因为这些格子里面有(i-1)个已经被填过了
然而这样只能过样例
这样填的话可能会出现一些情况,这些情况里不满足X格子只有给出的那么多
容斥一下
DFS搜出所有情况搞搞

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<cmath>
#define LL long long
using namespace std;
const int dx[8]={0,-1,-1,-1,0,1,1,1};
const int dy[8]={-1,-1,0,1,1,1,0,-1};
const int mod=12345678;
int num[(1<<10)],f[30][(1<<10)],n,m;
int vis[10][10],gg[10][10],bin[25],rem,ans;
int id[10][10];
bool check(int u,int v,int tt)
{
	if(vis[u][v]&&(tt&bin[id[u][v]]))return true;
	else if(vis[u][v])return false;
	for(int i=0;i<8;i++)
	{
		int pp=u+dx[i],qq=v+dy[i];
		if(pp<1||pp>n||qq<1||qq>m)continue;
		if(vis[pp][qq]&&(!(tt&bin[id[pp][qq]])))return false;
	}
	return true;
}
void init()
{
	memset(id,0,sizeof(id));
	rem=0;memset(num,0,sizeof(num));memset(f,0,sizeof(f));
	for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)if(vis[i][j])id[i][j]=++rem;
	for(int i=0;i<bin[rem+1];i++)
	{
		int sum=0;
		for(int j=1;j<=n;j++)
			for(int k=1;k<=m;k++)if(check(j,k,i))sum++;
		num[i]=sum;
	}
}
void dl(int &x,int y){x-=y;if(x<0)x+=mod;}
void ad(int &x,int y){x+=y;if(x>mod)x-=mod;}
void sol(int op)
{
	init();f[0][0]=1;
	for(int i=1;i<=n*m;i++)
		for(int j=0;j<=bin[rem+1]-1;j++)
		{
			int ct=0;
			for(int k=1;k<=rem;k++)if(j&bin[k])ct++;
			if(ct>=i)continue;
			for(int k=1;k<=rem;k++)//填x位置 
				if(!(j&bin[k]))//这个位置可以填
					f[i][j^bin[k]]=(f[i][j^bin[k]]+f[i-1][j])%mod;
			f[i][j]=(f[i][j]+(LL)f[i-1][j]*max((num[j]-i+1),0)%(LL)mod)%mod;
		}
	if(op%2)dl(ans,f[n*m][bin[rem+1]-1]);
	else ad(ans,f[n*m][bin[rem+1]-1]);
}
bool can_go(int u,int v)
{
	for(int i=0;i<8;i++)
	{
		int pp=u+dx[i],qq=v+dy[i];
		if(pp<1||pp>n||qq<1||qq>m)continue;
		if(vis[pp][qq])return false;
	}
	return true;
}
void dfs(int x,int y,int tim)
{
	if(x==n+1)
	{
		sol(tim);
		return ;
	}
	if(vis[x][y])dfs(x,y+1,tim);
	else if(y==m+1)dfs(x+1,1,tim);
	else
	{
		if(can_go(x,y))
		{
			vis[x][y]=1;
			dfs(x,y+1,tim+1);
			vis[x][y]=0;
		}
		dfs(x,y+1,tim);
	}
}
char ch[10];
int main()
{
//	freopen("a.in","r",stdin);
	bin[1]=1;for(int i=2;i<=20;i++)bin[i]=bin[i-1]<<1;
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
	{
		scanf("%s",ch+1);
		for(int j=1;j<=m;j++)if(ch[j]=='X')vis[i][j]=1;
	}
	dfs(1,1,0);
	printf("%d\n",ans);
	return 0;
}

猜你喜欢

转载自blog.csdn.net/Rose_max/article/details/82763658
今日推荐