ZOJ 3209 Treasure Map (DLX精确覆盖)

来源:互联网 发布:自学php能找到工作吗 编辑:程序博客网 时间:2024/05/17 02:54

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 integerT (T <= 500), indicating the number of cases.

For each case, the first line contains three integersn m p (1 <= n, m <= 30, 1 <= p <= 500), the width and the height of the map, and the number of pieces. Thenp 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

35 5 10 0 5 55 5 20 0 3 52 0 5 530 30 50 0 30 100 10 30 200 20 30 300 0 15 3015 0 30 30

Sample Output

1-12

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. 

题意为给定一个宽*高=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) ,  一开始要将一个大矩形里面每个坐标为i,j的小格子转换为在大矩形里面编号是多少,每一行投射的时候根据小矩形的坐标,两重循环,把小格子一一得投射到该行上(也就是找每个小格子对应该行的第几列)。当把p个小矩形都投射到对应的行以后,就变成了01矩阵,问题转化为从中抽取多少行,可以使这些行组成的新矩阵每一列都有一个1 ( 因为列的编号是1-n*m,大矩形分成的小格子编号也是1-n*m,要想完全覆盖,必须每一列都有一个1), 且每一列只有一个1(因为两个小矩形不可以交叉).转化号以后就可以用DLX来求解了。只套用模板是不行的,模板中是找到就返回,而本题是要找最小的,找到一个答案以后还得继续找,不能返回。


#include <iostream>
#include <string.h>
using namespace std;
struct DLX
{
    int n,m,size;
    int Up[450010],Down[450010],Right[450010],Left[450010],Row[450010],Col[450010];
    int H[501],S[901];//H是行头节点(共p行) S是每列有多少个节点
    int ansd;//如果有答案了,则一共选择了ansd行
    void inite(int _n,int _m)
    {
        n=_n;
        m=_m;
        for(int i=0;i<=m;i++)
        {
            S[i]=0;
            Up[i]=Down[i]=i;//初始状态时上下都指向自己
            Left[i]=i-1;
            Right[i]=i+1;
        }
        Right[m]=0;Left[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
        Down[size]=Down[c];//下面这四句是头插法(图是倒着的)
        Up[Down[c]]=size;
        Up[size]=c;
        Down[c]=size;
        if(H[r]<0)H[r]=Left[size]=Right[size]=size;
        else
        {
            Right[size]=Right[H[r]];
            Left[Right[H[r]]]=size;
            Left[size]=H[r];
            Right[H[r]]=size;
        }
    }
    void remove(int c)//删除节点c,以及c上下节点所在的行,每次调用这个函数,都是从列头结点开始向下删除
                      //这里c也可以理解为第c列,因为第c列的列头结点编号为c
    {
        Left[Right[c]]=Left[c];
        Right[Left[c]]=Right[c];
        for(int i=Down[c];i!=c;i=Down[i])
            for(int j=Right[i];j!=i;j=Right[j])
            {
                Up[Down[j]]=Up[j];
                Down[Up[j]]=Down[j];
                S[Col[j]]--;
            }
    }
    void resume(int c)//恢复节点c以及c上下节点所在的行(也可以理解为从第c列的头结点开始恢复)
    {
        for(int i=Up[c];i!=c;i=Up[i])
            for(int j=Left[i];j!=i;j=Left[j])
                ++S[Col[Up[Down[j]]=Down[Up[j]]=j]];
        Left[Right[c]]=Right[Left[c]]=c;
    }
    void Dance(int d)//递归深度
    {
        if(ansd!=-1&&ansd<=d)return;//剪枝,行数最少,之前找到一组答案ansd<=d,当前就不用在向下递归查找了
        if(Right[0]==0)
        {
            if(ansd==-1)ansd=d;
            else if(d<ansd)ansd=d;//要求最少用多少块
            else return;
        }
        int c=Right[0];
        for(int i=Right[0];i!=0;i=Right[i])
            if(S[i]<S[c])
                c=i;
        remove(c);//找到节点数最少的列,当前元素不是原图上01的节点,而是列头结点
        for(int i=Down[c];i!=c;i=Down[i])
        {
            for(int j=Right[i];j!=i;j=Right[j])
                remove(Col[j]);
            Dance(d+1);//找到返回
            for(int j=Left[i];j!=i;j=Left[j])
                resume(Col[j]);
        }
        resume(c);
    }
}g;


int main()
{
    int T;
    cin>>T;
    while(T--)
    {
        int n,m,p;
        cin>>n>>m>>p;
        g.inite(p,n*m);
        for(int k=1;k<=p;k++)
        {
            int x1,y1,x2,y2;
            cin>>x1>>y1>>x2>>y2;
            for(int i=x1+1;i<=x2;i++)
                for(int j=y1+1;j<=y2;j++)
                g.Link(k,j+(i-1)*m);
        }
        g.ansd=-1;
        g.Dance(0);
        cout<<g.ansd<<endl;
    }
    return 0;
}


0 0