POJ 2836 状压 DP

大致题意

用边平行于坐标轴的矩形来覆盖 n 个点,矩形至少要包含 2 个点(包括点位于边上的情况),矩形面积不能为 0 且顶点坐标都是整数。求覆盖全部点所用矩形的最小面积。2 ≤ n ≤ 15, −1,000 ≤ x, y ≤ 1,000

对覆盖的点进行状态压缩。首先确定搜索的子集,即矩形覆盖的点。求最小面积,矩形顶点至少有 2 个是需要覆盖的坐标点。遍历一遍 2 个点的组合,共 n * (n - 1) / 2 个,按位保存矩形覆盖的点,即可得到搜索的子集。

有部分矩形看起来是无用的,因为最小面积的求解中矩形一定是不相互覆盖的,也没有点是位于矩形顶点之外位置的。以上 2 种情况都可以找到更小的矩形面积,所以预处理时可以将这类矩形从遍历的对象中排除,以减小搜索次数。考虑最坏情况,即矩形 4 个顶点都覆盖了点,那么若某个矩形覆盖了超过 4 个以上的点,即可判断它不是最优解的可能搜索对象。对于题设矩形而言,边长一定大于等于 1 ,考虑边界情况,即 2 个顶点 x 或 y 轴坐标值相等,矩形退化成边,那么建立矩形对象的时候: ①求最小面积,即等于这条边的长度。因为某一边长度必须大于等于 1,可以用 max 方便地处理。②求覆盖点的子集。假设 2 个点坐标为 (x, y1), (x, y2),那么矩形第 3 个顶点可能的位置是 (x - 1, y1) 或 (x + 1, y1),有 2 种情况。实际上,只要对所有 x 轴坐标值相等的情况统一处理成 x + 1 或者 x - 1,即可覆盖所有最优解的可能子集。

状态转移 dp[s | sub] = min(dp[s | sub], dp[s] + area) ,dp[s] 即覆盖的点集合为 s 时矩形的最小面积。因为最优解的矩形间可能出现顶点相交(考虑 3 个点的情况就明白了),所以不能用 dp[s ^ sub] 转移状态。

#include <cstdio>
#include <STDLIB.H>
#include <algorithm>
#include <iostream>
#define min(a,b)    (((a) < (b)) ? (a) : (b))
#define max(a,b)    (((a) > (b)) ? (a) : (b))
#define abs(x)    ((x) < 0 ? -(x) : (x))
#define INF 0x3f3f3f3f
#define eps 1e-4
#define M_PI 3.14159265358979323846
#define MAX_N 15
using namespace std;
struct rect{
	int sub, area;
	rect(int sub, int area) : sub(sub), area(area) {}
	rect(){}
};
int N, M;
int X[MAX_N], Y[MAX_N];
rect R[MAX_N * (MAX_N - 1) / 2];
int dp[1 << MAX_N];

int calc(int i, int j){
	return max(1, abs(X[i] - X[j])) * max(1, abs(Y[i] - Y[j]));
}
void init(){
	for(int i = 0; i < N; i++) scanf("%d%d", X + i, Y + i); 
	M = 0;
	for(int i = 0; i < N; i++){
		for(int j = i + 1; j < N; j++){
			int x1 = X[i], y1 = Y[i], x2 = X[j], y2 = Y[j];
			if(x1 == x2) ++x1;
			else if(y1 == y2) ++y1;
			int cnt = 0, sub = 0;
			for(int k = 0; k < N; k++){
				if(((X[k] - x1) * (X[k] - x2) <= 0) && ((Y[k] - y1) * (Y[k] - y2) <= 0)){
					++cnt, sub |= 1 << k;
				}
			}
			if(cnt <= 4) R[M++] = rect(sub, calc(i, j));
		}
	}
}
void solve(){
	memset(dp, 0x3f, sizeof(dp));
	dp[0] = 0;
	for(int i = 0; i < M; i++){
		for(int s = 0; s < 1 << N; s++){
			if(dp[s] != INF){
				int nxt = s | R[i].sub;
				if(nxt != s){
					dp[nxt] = min(dp[nxt], dp[s] + R[i].area);
				}
			}
		}
	}
	printf("%d\n", dp[(1 << N) - 1]);
}
int main(){
	while(~scanf("%d", &N) && N){
		init();
		solve();
	}
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值