poj2446

題目比較有意思,大致題意:給定一個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;
}


 

發佈了209 篇原創文章 · 獲贊 20 · 訪問量 14萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章