对于一般的dp(除了数位dp),在思考状态转移方程的时候,一般有两种思路。
- 用过去的最优解推当前的最优解
- 用当前的最优解去推未来的最优解
具体用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;
}