poj2446

3 篇文章 0 订阅
1 篇文章 0 订阅

题目比较有意思,大致题意:给定一个n*m矩阵,并给定k个填充物的坐标,坐标即(列号、行号)。要求用1*2的小矩阵填充给定的n*m矩阵,要求不能填充区域不能覆盖k个填充物的区域。

刚开始以为是经典dp。后来发现多了一个条件:填充物。本来是压缩状态dp,可以通过0、1二进制转化状态,现在多了一个填充物,那么原先的3种情况:

1)在前一行横铺的情况下(即为1)后一行同一列可以横铺,要求下一列必须也横铺(即均为1)并且前一行下一列对应的状态必须为0(即横铺)

2)在前一行横铺的情况下(即为1)后一行同一列可以纵铺即为0

3)在前一行纵铺的情况下(即为0)后一行同一列必须为1(即纵铺延展)

多了一种情况:即在前一行为填充物的情况下,后一行同一列可以为横铺也可以为纵铺还可以为填充物,这样的化就不好用二进制进行状态压缩了。

故这里转化思路:二分图解决。

分析如下:

首先对于题目,由于有了k个填充物的存在,所以要想将1*2的矩阵进行填充,且填充满矩阵。则m*n-k就必须为偶数,这是第一条件。在该条件满足后,我们不妨将矩阵看成是从行号从1到n,列号从1到m的矩阵。而每一个1*1小方格的权值记为i+j,其中i为行号,j为列号。这样以后通过观察可以发现:1*2的矩阵所占用的两个连续的1*1小方格必定一个权值为奇数,一个权值为偶数。这样我们可以将权值为奇数的小方格从1开始编号,对偶数权值的小方格也同样处理。这样以后,其实题目就出来了:由于1*2的矩阵必定占用一个权值为奇数,一个权值为偶数的方格。我们可以将这个1*2的小矩阵看成是一条边,而其顶点则依次是奇数方格和偶数方格。而若想将全部矩阵填充满这样的不同边数就必须要为(n*m-k)/2条。

也即将奇数权值顶点转化为一个集合,将偶数权值顶点同样转化为一个集合,若奇数权值顶点和偶数权值顶点可以连接,则设置相应的边。然后就是求最大二分匹配S了。若最大二分匹配小于(n*m-k)/2则说明不能填充满,若相等则说明可以填充满(不可能大于(n*m-k)/2)。

此题还要注意两点:

1)输入k个填充物时,先输入的是列号,后输入的是行号,注意转化
2)在转化边的时候,若遇到是填充物的情况。可直接跳过,因为不可能在填充物处形成小矩形。

下面是代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define Max 40
int dir[4][2]={{-1,0},{1,0},{0,-1},{0,1}}; //边相连的四种情况
bool flag[Max][Max]; // 标记是否为填充物(true为填充物,false不是填充物)
int cal[Max][Max]; // 标记源矩阵坐标为(i,j)的1*1小方格的偶数编号或奇数编号(均从1开始)
bool match[Max*Max/2][Max*Max/2]; // 标记转化为二部图后的边匹配情况
int pre[Max*Max/2]; //标记匹配顶点标号
bool vis[Max*Max/2]; //标记是否已经访问
int n,m,k; //行号、列号、填充物个数
int dou,sin; // 偶数权值顶点个数、奇数权值顶点个数
bool find(int x){//深搜寻找增广路径
	for(int i=1;i<=sin;i++){
		if(!vis[i] && match[x][i]){
			vis[i]=true;
			if(pre[i]==-1 || find(pre[i])){
				pre[i]=x;
				return true;
			}
		}
	}
	return false;
}
int main(){
	scanf("%d%d%d",&n,&m,&k);
	if((n*m-k)%2!=0){ //若不满足第一个条件,则直接输出“NO”
		int a,b;
		while(k--)
			scanf("%d%d",&a,&b);
		printf("NO\n");
	}
	else{
		memset(flag,0,sizeof(flag)); //初始话为均不为填充物
		int a,b;
		for(int i=1;i<=k;i++){ //标记填充物
			scanf("%d%d",&a,&b);
			flag[b][a]=true;
		}
		sin=0,dou=0; //初始化编号从1开始
		memset(match,0,sizeof(match)); //初始化没有边
		for(int i=1;i<=n;i++)
			for(int j=1;j<=m;j++) //根据奇偶数权值情况重新编号
				if(((i+j)&1))
					cal[i][j]=++sin;
				else
					cal[i][j]=++dou;
		for(int i=1;i<=n;i++) // 根据相连情况,给二部图加边
			for(int j=1;j<=m;j++)
				if(!((i+j)&1) && !flag[i][j]){ // 若为偶数且不是填充物
					for(int k=0;k<4;k++){ //四个方向
						int xx=dir[k][0]+i,yy=dir[k][1]+j; 
						if(xx>=1 && xx<=n && yy>=1 && yy<=m && !flag[xx][yy])//若在方格内,且不是填充物
							match[cal[i][j]][cal[xx][yy]]=true; // 加边
					}
				}
		int ans=0; //匈牙利算法求最大二分匹配
		memset(pre,-1,sizeof(pre));
		for(int i=1;i<=dou;i++){
		    memset(vis,0,sizeof(vis));
			ans+=find(i);
		}
		if(ans==(n*m-k)/2) //若可以填充满
			printf("YES\n");
		else
			printf("NO\n");
	}
	return 0;
}


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值