POJ 2836 Rectangular Covering 状态压缩DP(铺砖问题)

一、题目大意

坐标系中有n个点,它们满足 -1000<=x<=1000,-1000<=y<=1000。

现在要在坐标系中放一些矩形,要使得每个点都被矩形覆盖(被矩形的边或者顶点覆盖也可以),每个矩形都必须满足面积大于0,且每个矩形最少要覆盖两个点。

请你输出覆盖所有点时,如何使所有矩形的面积和最小,输出这个面积和。

二、解题思路

我们可以从左上角的点开始,一点点的覆盖到右下角的点。

那么对于第 i 个点,我们可以认为

1、i 以前的点都被覆盖;

2、如果 i 还未被覆盖,则它必须被覆盖。

同时思考下,当铺设到第 i 个点时,对于覆盖情况固定时,铺完剩下的点需要的最小矩形面积是固定的。

则可以定义DP数组,DP[S][i]代表已经铺设的点的集合为S时,铺设好第 i 个点到最后一个点所需要的最小的矩形面积。

对于最后一个点n-1,如果S包含n-1,则DP[S][n-1]=0,如果不包含,那么DP[S][n-1]=点n-1到其余点的矩形的面积的最小值。

那么借助这个思路,我们就可以展开循环,外层放 i ,从 n - 1 到 0循环,内层放 S,从 1 到 (1<<n)-1循环,当S包含 i 时,则DP[S][i]=DP[S][i+1]。

如果S不包含 i ,就循环其他所有点 j (i != j),

DP[S][i]=min(DP[S][i],DP[S 添加 i 添加 j][i+1]+i和j组成的矩形面积)

思路可以了,但欠缺一点,有时候一个矩形会覆盖两个点。

这种情况仅在两个点同行或同列的情况出现,否则是下图

那么加个判断,如果两个点是同一条直线,它们之间的矩形为零的那条边设置长度为1。同时连一条边的时候,判断是否有其他点是否也在矩形内的,记录它们到集合child里,更改递推式为

DP[S][i]=min(DP[S][i],DP[S 添加 i 添加 j 添加child内所有的元素][i+1]+i和j组成的矩形面积)

(这里只判断 i 之后的点是否在范围内即可,因为之前的不会影响到DP[XXX][i+1]之后的值)

循环集合时,要跳过确定已经连了的部分来提速。

三、代码

#include <iostream>
#include <algorithm>
#include <string.h>
using namespace std;
struct P
{
    int x, y;
    P(int x = 0, int y = 0) : x(x), y(y) {}
};
bool compare(P &a, P &b)
{
    return a.y != b.y ? a.y > b.y : a.x < b.x;
}
const int MAX_N = 15;
const int INF = 0x3f3f3f3f;
int dp[2][1 << MAX_N];
P dat[15];
int n;
int *crt = dp[0], *nxt = dp[1];
void solveItem(int i, int used)
{
    if (used >> i & 1)
    {
        nxt[used] = crt[used];
        return;
    }
    nxt[used] = INF;
    for (int j = 0; j < n; j++)
    {
        if (i == j)
        {
            continue;
        }
        int w = abs(dat[i].x - dat[j].x);
        int h = abs(dat[i].y - dat[j].y);
        int x1 = min(dat[i].x, dat[j].x);
        int y1 = min(dat[i].y, dat[j].y);
        int area = (w > 0 ? w : 1) * (h > 0 ? h : 1);
        int child = 0;
        for (int k = i + 1; k < n; k++)
        {
            if ((dat[k].x >= x1 && dat[k].x <= (x1 + w) && dat[k].y >= y1 && dat[k].y <= (y1 + h)) ||
                (w == 0 && dat[k].x >= (x1 - 1) && dat[k].x <= x1 && dat[k].y >= y1 && dat[k].y <= (y1 + h)) ||
                (h == 0 && dat[k].y >= (y1 - 1) && dat[k].y <= y1 && dat[k].x >= x1 && dat[k].x <= (x1 + w)))
            {
                child = child | (1 << k);
            }
        }
        nxt[used] = min(nxt[used], crt[used | (1 << i) | (1 << j) | child] + area);
    }
}
void solve()
{
    memset(dp, 0, sizeof(dp));
    int all = (1 << n) - 1;
    for (int i = n - 1; i >= 0; i--)
    {
        all = all & ~(1 << i);
        for (int S = 0; S < (1 << (n - i)); S++)
        {
            int used = all | (S << i);
            solveItem(i, used);
        }
        swap(crt, nxt);
    }
    printf("%d\n", crt[0]);
}
int main()
{
    while (true)
    {
        scanf("%d", &n);
        if (n == 0)
        {
            break;
        }
        for (int i = 0; i < n; i++)
        {
            scanf("%d%d", &dat[i].x, &dat[i].y);
        }
        sort(dat, dat + n, compare);
        solve();
    }
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
题目描述 给出一个$n\times m$的矩阵,每个位置上有一个非负整数,代表这个位置的海拔高度。一开始时,有一个人站在其中一个位置上。这个人可以向上、下、左、右四个方向移动,但是只能移动到海拔高度比当前位置低或者相等的位置上。一次移动只能移动一个单位长度。定义一个位置为“山顶”,当且仅当从这个位置开始移动,可以一直走到海拔高度比它低的位置上。请问,这个矩阵中最多有多少个“山顶”? 输入格式 第一行两个整数,分别表示$n$和$m$。 接下来$n$行,每行$m$个整数,表示整个矩阵。 输出格式 输出一个整数,表示最多有多少个“山顶”。 样例输入 4 4 3 2 1 4 2 3 4 3 5 6 7 8 4 5 6 7 样例输出 5 算法1 (递归dp) $O(nm)$ 对于这道题,我们可以使用递归DP来解决,用$f(i,j)$表示以$(i,j)$为起点的路径最大长度,那么最后的答案就是所有$f(i,j)$中的最大值。 状态转移方程如下: $$ f(i,j)=\max f(x,y)+1(x,y)是(i,j)的下一个满足条件的位置 $$ 注意:这里的状态转移方程中的$x,y$是在枚举四个方向时得到的下一个位置,即: - 向上:$(i-1,j)$ - 向下:$(i+1,j)$ - 向左:$(i,j-1)$ - 向右:$(i,j+1)$ 实现过程中需要注意以下几点: - 每个点都需要搜一遍,因此需要用双重for循环来枚举每个起点; - 对于已经搜索过的点,需要用一个数组$vis$来记录,防止重复搜索; - 在进行状态转移时,需要判断移动后的点是否满足条件。 时间复杂度 状态数为$O(nm)$,每个状态转移的时间复杂度为$O(1)$,因此总时间复杂度为$O(nm)$。 参考文献 C++ 代码 算法2 (动态规划) $O(nm)$ 动态规划的思路与递归DP类似,只不过转移方程和实现方式有所不同。 状态转移方程如下: $$ f(i,j)=\max f(x,y)+1(x,y)是(i,j)的下一个满足条件的位置 $$ 注意:这里的状态转移方程中的$x,y$是在枚举四个方向时得到的下一个位置,即: - 向上:$(i-1,j)$ - 向下:$(i+1,j)$ - 向左:$(i,j-1)$ - 向右:$(i,j+1)$ 实现过程中需要注意以下几点: - 每个点都需要搜一遍,因此需要用双重for循环来枚举每个起点; - 对于已经搜索过的点,需要用一个数组$vis$来记录,防止重复搜索; - 在进行状态转移时,需要判断移动后的点是否满足条件。 时间复杂度 状态数为$O(nm)$,每个状态转移的时间复杂度为$O(1)$,因此总时间复杂度为$O(nm)$。 参考文献 C++ 代码

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值