poj2446

来源:互联网 发布:表提交给两个php 编辑:程序博客网 时间:2024/05/17 01:32

题目比较有意思,大致题意:给定一个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 40int 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;elsecal[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");elseprintf("NO\n");}return 0;}


 

0 0