A - Treasure Map ZOJ - 3209(DLX精确覆盖)

A - Treasure Map ZOJ - 3209

思路

  • 题意
  1. 给我们一个 大矩形,这个矩形的左下角为(0,0),右上角坐标为(n,m)
  2. 现在又给我们k个小矩形,它左下角坐标为(x1,y1),右上角为(x2,y2),现在我们要用这个k个小矩形的中的一些,来拼凑出的完整的大矩形,注意:拼凑的大矩形不能有重叠部分
  • 分析
  1. 这个 既然要用 DLX来解决重复覆盖问题,我们先弄明白精确覆盖问题的定义:给定一个由0-1组成的矩阵,是否能找到一个行的集合,使得集合中每一列都恰好包含一个1
  2. 首先我们考虑:要怎么构造出一个"数据矩形",通过这个数据矩形,把题目的问题转化为精确覆盖问题
  3. 怎么构造那?,我们注意这句话:“使得集合中每一列都恰好包含一个1”, 对于这个要求我们可以结合题目中的要求:“拼凑的大矩形不能有重叠部分”,不能有重叠那意味着 对于大矩形的 任意一个 1x1单位矩形,只能被覆盖1次,那么我们可以让这个大矩形的 n*m个单位方格,来作为我们要构造的"数据矩形的" 列,这样我们在合理的选择一些行之后,就能使 我们选择出来的集合的每一列,只有一个1出现了 == 等价与 我们把 大矩形 没有重复的 拼凑出来了
  4. 在注意:“是否能找到一个行的集合”,那么我们考虑,让什么当做“行”,我们把 所给的 小矩形中所出现的 单位方格当做1,其他没有在这个小矩形中 出现的单位矩形 看做0,这样就拼凑一个“k行 ,m*n列的数据矩形了”
  5. 具体做法:
    1. 我们给大矩形:的每个单位方格 从1到m*n进行 编号,这个每个单位矩形的编号就是它所在的列号,
    2. 对于k个中的任意一个 小矩形,它都是大矩形的一个子集,那么小矩形 中的单位矩形,必然也是被编了号的,那么小矩形的中的某个单位矩形被编了什么号,那么那个单位小矩形就出现 在那一列,在对应的 数据矩形行、列上就是1,那么剩下的其它位置默认为0,这样就构造好了,之后套模版就行了

代码

#include<iostream> 
#include<algorithm>
#include<cstring>
#include<queue>
#include<map>
#include<string>
#include<cstdio>
#include<cmath>
#include<stack>
void fre() { freopen("A.txt", "r", stdin); freopen("Ans.txt","w",stdout); } void Fre() { freopen("A.txt", "r", stdin);}
#define ios ios::sync_with_stdio(false)
#define Pi acos(-1)
#define pb push_back
#define fi first
#define se second
#define ll long long
#define ull unsigned long long 
#define db double
#define Pir pair<int, int>
#define PIR pair<Pir, Pir>
#define m_p make_pair
#define INF 0x3f3f3f3f
#define mod (ll)(1e9 + 7)
#define for_(i, s, e) for(int i = (ll)(s); i <= (ll)(e); i ++)
#define rep_(i, e, s) for(int i = (ll)(e); i >= (ll)(s); i --)
#define sd(a) scanf("%d", &a)
#define sc(a) scanf("%c", &a)
using namespace std;

const int mxnode = 450010;
const int mxm = 1e3 + 10;
const int mxn = 510;

struct DLX
{
    int n, m, size;     //地图尺寸n行m列,size为图中给图中的 1 的编号
    int U[mxnode], D[mxnode], L[mxnode], R[mxnode], Row[mxnode], Col[mxnode];
    int H[mxn];         //每行行头的节点
    int S[mxm];         //每列有几个节点
    int ansd, ans[mxn]; //如果有答案:则选择了ansd行,具体是那几行存储的ans中

    void init(int _n, int _m)
    {
        n = _n, m = _m;
        for_(i, 0, m)
        {
            S[i] = 0;
            U[i] = D[i] = i;        //初始状态,上下节点都指向自己的编号 i
            L[i] = i - 1;
            R[i] = i + 1;
        }
        R[m] = 0, L[0] = m;         //编号为0的节点为第一行第一个节点,它指向的节点为m;m为第一行最后一个节点它指向的0(第一行第一个节点)
        for_(i, 1, n)
            H[i] = -1;              //在第二行之后,行首节点就暂时无法确定是从哪一列的产生的
        size = m;                   //编号,每节点都有一个编号, m为第一行最后一个节点的编号,那么下一个节点的编号就是 ++ size==m+1
    }

    void link(int r, int c)        //第r行,c列
    {
        Row[++ size] = r;           //记录当前节点所在的行、列
        Col[size] = c;
        S[c] ++;                    //当前列的节点数量 ++

        D[size] = c;
        U[size] = U[c];
        D[U[c]] = size;
        U[c] = size;

        if(H[r] == -1)
            H[r] = L[size] = R[size] = size;        //初始化 头节点 以及 头结点左右节点 都是自己的编号
        else
        {
            L[size] = size - 1;
            R[size] = H[r];
            R[L[size]] = size;
            L[H[r]] = size;
        }
    }

    void remove(int c)          //删除节点c,以及在第c列有元素的行中的所有元素也要都删除
    {
        R[L[c]] = R[c];
        L[R[c]] = L[c];
        for(int i = D[c]; i != c; i = D[i])
            for(int j = R[i]; j != i; j = R[j])
                D[U[j]] = D[j], U[D[j]] = U[j], S[Col[j]] --;
    }

    void resume(int c)
    {
        R[L[c]] = c;
        L[R[c]] = c;
        for(int i = D[c]; i != c; i = D[i])
            for(int j = R[i]; j != i; j = R[j])
                D[U[j]] = U[D[j]] = j, S[Col[j]] ++;
    }

    void dance(int d)       //递归的深度
    {
        if(ansd != -1 && ansd <= d) return;     //已经找到一组答案ansd,不用继续往下递归答案了
        if(R[0] == 0)       //第一行辅助节点的只有一个,表明找到了一组答案ansd
        {
            if(ansd == -1) ansd = d;
            else if(ansd > d) ansd = d;
            return;
        }
        int c = R[0];       //找到拥有最小节点的列,从而减少递归的层数
        for(int i = R[0]; i; i = R[i])
            if(S[c] > S[i])
                c = i;
        remove(c);
        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);
    }
} dl;

int pos[35][35];
void get_pos(int n, int m)
{
    int idx = 1;
    for_(i, 1, n)
        for_(j, 1, m)
        pos[i][j] = idx ++;
}

int main()
{
    /* fre(); */
    int n, m, t;
    int p, x1, y1, x2, y2;
    sd(t);
    while(t --)
    {
        scanf("%d %d %d", &n, &m, &p);
        dl.init(p, n * m);
        get_pos(n, m);
        for_(k, 1, p)
        {
            scanf("%d %d %d %d", &x1, &y1, &x2, &y2);
            for_(i, x1+1, x2)
                for_(j, y1+1, y2)
                dl.link(k, pos[i][j]);
        }
        dl.ansd = -1;
        dl.dance(0);
        printf("%d\n", dl.ansd);
    }

    return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_34261446/article/details/107292155
ZOJ
今日推荐