poj 2836 + 一些关于dp的新看法

本文深入探讨动态规划中两种核心思路:从过去最优解推导当前最优解,及利用当前最优解预测未来最优解。通过01背包问题实例解析,并以一复杂问题对比两种策略的效率差异,展示其在实际编程竞赛中的应用与优化技巧。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

对于一般的dp(除了数位dp),在思考状态转移方程的时候,一般有两种思路。

  1. 用过去的最优解推当前的最优解
  2. 用当前的最优解去推未来的最优解

具体用01背包来演示一下
第一种

for (int i = 0; i < n; i++) 
		for (int j = v; j >= 0; j--) 
			dp[j] = max(dp[j], dp[j - c[i]] + w[i]);

dp[v]是当前状态,dp[v-c[i]]是过去状态,这种思路就是过去推现在。

第二种

for (int i = 0; i <=v; i++) 
		for (int j = 0; j < n; j++) 
			dp[j + c[i]] = max(dp[j + c[i]], dp[j] + w[i]);
//略微简略,明白意思就好

dp[v]是当前状态,dp[v + c[i]]是未来状态。这种思路就是现在推未来。

对于01背包问题,显然这两种思路都是正确的。不过有的题就不是这么幸运了。

题意:
给你n个点,让你用任意个边长为整数的矩形包裹起来(边要与坐标轴平行),问所有矩形面积之和的最小值是多少?

输入
2 //表示两个点
0 1 //点
1 0 //点
0 //表示输入结束

输出
1 //表示答案

思路:
先把点两两组合,任意两个点构成一个矩形(一共构成n*(n-1)/2个矩形),这个矩形中间可能包含其他点,然后记录这个矩形的面积和包含那些点(用二进制表示)。然后就可以跑dp了

dp[i]定义:i的二进制位表示包含这些点的时候的最小面积。这样任意的dp[i]都是由多个矩形组合构成的,再进一步,任意的dp[i]都是由某个矩形和dp[j]构成,这样就有了状态转移。

状态转移:
这时候我们之前讲个两种思路就派上用场了,如果使用第一种思路,假设要求dp[i],那么我们就要在已知a和i的情况下,找出所有a|b=i的解,因为上面说了,dp[i]是由一个矩形和dp[j]组成,那么代码就就应该这样写

for (int i = 0; i < (1 << n); i++) {
			for (int j = 0; j < rect.size(); j++) {
				//因为我要求dp[i],
				//意思是a|b=i,我已知a和i,
				//所以我不得不再来一个循环取找b
				for (int k = 0; k < (1 << n); k++) {
					//cover的二进制位表示这个矩形包含那些点
					//area表示这个矩形的面积
					if (k | rect[j].cover == i) {
						dp[i] = min(dp[i], dp[k] + rect[j].area);
					}
				}
			}
		}

这样做的复杂度是O(n^2 * 2^n * 2^n)因为n=14,所以算出来循环5*10的10次方。超时。

如果用第二种思路,假设已知dp[b],那么dp[b]能得到哪些未来状态呢?很显然dp[b]和任意矩形组合就可以得到一个未来的状态 ,相当于在已知a和b的情况下求a|b=i的解,这就很容易了
写成代码应该是这样

for (int i = 0; i < rect.size(); i++)
			for (int j = 0; j < (1 << n); j++) {
				int future = j | rect[i].cover;
				dp[future] = min(dp[future],dp[j] + rect[i].area);
			}

这时候复杂度是O(n^2 * 2^n * 1),为什么复杂度要1,因为这个脑残编辑器不1他就显示不出来。循环次数10的7次方。能ac;

下面是完整代码

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <iostream>
#include <iomanip>
#include <string>
#include <algorithm>
#include <stack>
#include <queue>
#include <set>
#include <vector>
#include <map>

#define INF 0x3f3f3f3f
#define LINF 0x3f3f3f3f3f3f3f3f
#define ll long long
#define ull unsigned long long
#define uint unsigned int

using namespace std;

struct Rect {
	int area, cover;
	Rect(int a,int c):area(a),cover(c){}
	void add(int x) { cover |= x; }
};

int n, x[20], y[20],dp[1<<20];

int main(){
	while (scanf("%d", &n) != EOF && n) {
		for (int i = 0; i < n; i++)
			scanf("%d%d", x + i, y + i);

		vector<Rect> rect;
		for (int i = 0; i < n; i++) 
			for (int j = i + 1; j < n; j++) {
				Rect temp(max(1, abs(x[i] - x[j]))*max(1, abs(y[i] - y[j])),
					(1 << i) | (1 << j));
				for (int k = 0; k < n; k++)
					if (((x[i] - x[k]) * (x[j] - x[k]) <= 0) && ((y[i] - y[k]) * (y[j] - y[k]) <= 0))
						temp.add(1 << k);
				rect.push_back(temp);
			}

		memset(dp, INF, sizeof(dp));
		dp[0] = 0;
		for (int i = 0; i < rect.size(); i++)
			for (int j = 0; j < (1 << n); j++) {
				int future = j | rect[i].cover;
				dp[future] = min(dp[future],dp[j] + rect[i].area);
			}
				
			
		printf("%d\n", dp[(1 << n) - 1]);
	}

	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值