首页 > 代码库 > [ACM] ZOJ 3209 Treasure Map ( Dancing Links 精确覆盖,矩形覆盖)
[ACM] ZOJ 3209 Treasure Map ( Dancing Links 精确覆盖,矩形覆盖)
Your boss once had got many copies of a treasure map. Unfortunately, all the copies are now broken to many rectangular pieces, and what make it worse, he has lost some of the pieces. Luckily, it is possible to figure out the position of each piece in the original map. Now the boss asks you, the talent programmer, to make a complete treasure map with these pieces. You need to make only one complete map and it is not necessary to use all the pieces. But remember, pieces are not allowed to overlap with each other (See sample 2).
Input
The first line of the input contains an integer T (T <= 500), indicating the number of cases.
For each case, the first line contains three integers n m p (1 <= n, m <= 30, 1 <= p <= 500), the width and the height of the map, and the number of pieces. Then p lines follow, each consists of four integers x1 y1 x2 y2 (0 <= x1 < x2 <= n, 0 <= y1 < y2 <= m), where (x1, y1) is the coordinate of the lower-left corner of the rectangular piece, and (x2, y2) is the coordinate of the upper-right corner in the original map.
Cases are separated by one blank line.
Output
If you can make a complete map with these pieces, output the least number of pieces you need to achieve this. If it is impossible to make one complete map, just output -1.Sample Input
3 5 5 1 0 0 5 5 5 5 2 0 0 3 5 2 0 5 5 30 30 5 0 0 30 10 0 10 30 20 0 20 30 30 0 0 15 30 15 0 30 30
Sample Output
1 -1 2
Hint
For sample 1, the only piece is a complete map.
For sample 2, the two pieces may overlap with each other, so you can not make a complete treasure map.
For sample 3, you can make a map by either use the first 3 pieces or the last 2 pieces, and the latter approach one needs less pieces.
Author: HANG, Hang
Source: The 6th Zhejiang Provincial Collegiate Programming Contest
解题思路:
题意为给定一个n*m的大矩形,再给出p个小矩形,给出左上角和右上角的坐标,问能不能从这些小矩形里面拿出一些完全覆盖掉给定的大矩形,要求小矩形覆盖时不能交叉,如果可以,求出最少需要多少个小矩形,否则输出-1.
本题可以转化为DLX完全覆盖问题,构造01矩阵。怎么构造呢?首先我们把给定的p个小矩形,每个看作一行,那么构造出的矩阵有p行,原来n*m的大矩形,可以分为n*m个小格,每个小格看作是一列,且每个小格都有一个编号(1- n*m),要想完全覆盖这个大矩形,那么编号1-n*m的所有格子都应该为1(被覆盖掉). 构造出的矩阵有n*m列, 那么就构造出了一个p行,n*m列的新矩阵。接下来就是把每一个小矩形拆成1*1的小格子,分别投射到对应的行里面的格子里面(格子设为1) , 一开始用pos[i][j] 数组记录下一个大矩形里面每个坐标为i,j的小格子在大矩形里面编号是多少,每一行投射的时候根据小矩形的坐标,两重循环,把小格子一一得投射到该行上(也就是找每个小格子对应该行的第几列)。当把p个小矩形都投射到对应的行以后,就变成了01矩阵,问题转化为从中抽取多少行,可以使这些行组成的新矩阵每一列都有一个1 ( 因为列的编号是1-n*m, 大矩形分成的小格子编号也是1-n*m, 要想完全覆盖,必须每一列都有一个1), 且每一列只有一个1(因为两个小矩形不可以交叉).转化号以后就可以用DLX来求解了。只套用模板是不行的,模板中是找到就返回,而本题是要找最小的,找到一个答案以后还得继续找,不能返回。
代码:
#include <iostream> #include <stdio.h> using namespace std; const int maxnode=450010; const int maxm=901; const int maxn=505; struct DLX { int n,m,size; int U[maxnode],D[maxnode],R[maxnode],L[maxnode],Row[maxnode],Col[maxnode]; int H[maxn];//行头节点 int S[maxm];//每列有多少个节点 int ansd,ans[maxn];//如果有答案,则选了ansd行,具体是哪几行放在ans[ ]数组里面,ans[0~ansd-1]; void init(int _n,int _m) { n=_n,m=_m; for(int i=0;i<=m;i++) { S[i]=0; U[i]=D[i]=i;//初始状态下,上下自己指向自己 L[i]=i-1; R[i]=i+1; } R[m]=0,L[0]=m; size=m;//编号,每列都有一个头节点,编号1-m for(int i=1;i<=n;i++) H[i]=-1;//每一行的头节点 } void link(int r,int c)//第r行,第c列 { ++S[Col[++size]=c];//第size个节点所在的列为c,当前列的节点数++ Row[size]=r;//第size个节点行位置为r D[size]=D[c];//下面这四句头插法(图是倒着的?) U[D[c]]=size; U[size]=c; D[c]=size; if(H[r]<0) H[r]=L[size]=R[size]=size; else { R[size]=R[H[r]]; L[R[H[r]]]=size; L[size]=H[r]; R[H[r]]=size; } } void remove(int c)//删除节点c,以及c上下节点所在的行,每次调用这个函数,都是从列头节点开始向下删除,这里c也可以理解为第c列 { //因为第c列的列头节点编号为c L[R[c]]=L[c]; R[L[c]]=R[c]; for(int i=D[c];i!=c;i=D[i]) for(int j=R[i];j!=i;j=R[j]) { U[D[j]]=U[j]; D[U[j]]=D[j]; --S[Col[j]]; } } void resume(int c)//恢复节点c,以及c上下节点所在的行(同上,也可以理解为从第c列的头节点开始恢复 { for(int i=U[c];i!=c;i=U[i]) for(int j=L[i];j!=i;j=L[j]) ++S[Col[U[D[j]]=D[U[j]]=j]]; //打这一行太纠结了 T T L[R[c]]=R[L[c]]=c; } void dance(int d)//递归深度 { if(ansd!=-1&&ansd<=d)//行数最少,以前找到一组答案ansd<=d,当前就不用再向下递归查找了 return; if(R[0]==0) { if(ansd==-1)ansd=d; else if(d<ansd)//要求最少用多少块 ansd=d; return ; } int c=R[0]; for(int i=R[0];i!=0;i=R[i]) if(S[i]<S[c]) c=i; remove(c);//找到节点数最少的列,当前元素不是原图上0,1的节点,而是列头节点 for(int i=D[c];i!=c;i=D[i]) { ans[d]=Row[i];//列头节点下面的一个节点 for(int j=R[i];j!=i;j=R[j]) remove(Col[j]); dance(d+1);//找到,返回 for(int j=L[i];j!=i;j=L[j]) resume(Col[j]); } resume(c); } }; DLX x; int n,m; int p,x1,y1,x2,y2; int pos[32][32];//每个长方形的每个小格在大长方形里的编号 int id; void getPos(int n,int m) { id=1; for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) pos[i][j]=id++; } int main() { int t;scanf("%d",&t); while(t--) { scanf("%d%d%d",&n,&m,&p); x.init(p,n*m); getPos(n,m); for(int r=1;r<=p;r++) { scanf("%d%d%d%d",&x1,&y1,&x2,&y2); for(int i=x1+1;i<=x2;i++) for(int j=y1+1;j<=y2;j++) x.link(r,pos[i][j]); } x.ansd=-1; x.dance(0); printf("%d\n",x.ansd); } return 0; }
[ACM] ZOJ 3209 Treasure Map ( Dancing Links 精确覆盖,矩形覆盖)