满满的都是爱,蓝回的那么快,五秒规则也信手拈来.....
好像扯远了.....
本章讲的是很基础的由动归引深来的背包问题,如果你是算法大人.....去该干嘛干嘛吧......因为本章是为了那些面试学生写的。
首先声明,本章讲的是《背包九讲》的前三章,只不过从一个面试者角度去写,能很好地让那些该面试而不知道的学生了解这个算法,而不会深究算法的推到和时间复杂度问题。
在此,先热烈庆祝一下巫航同学被松下公司录取,以后买电器有打折的地方了~~~~
好,我们来切入正题,背包问题是很多面试公司问及算法时候喜欢问的,因为该问题源于动归,而且是一个很成型的问题,所以本文选取“01背包”、“完全背包”和“多重背包”去介绍,如果你面试的公司问了“依赖背包”或各种变异体.....那么你面试的是google吧,面试google连背包都不知道,你肯定在闹.....
背包问题是什么呢?算法课上老师肯定都说过:我有一个体积为V的包,有N个物品,每个物品有价值wi和体积ti,我想把东西放进包里让其所装的物品总价值尽可能的大。
如果你回答是使劲往里塞,塞到溢出来为止,那么,个人建议你不适合搞计算机,你应该去考公务员。
于是,我们来引入01背包思想。
01,顾名思义,就是两种状态,取或不取,于是我们不难联想到前面学过的搜索.....不过2^N这个复杂度........我这不是Intel超极本.....
所以,我们需要一个表达式级别的复杂度来友好的表示取或不取。
初始状态,我们背包是空,也就是0这个状态是必有的,之后我们往里装了一个体积为ti的物品,这时候,就变成了0和0 + ti这两个状态可到达了,于是再来一个tj物品,变成了0,0 + ti,0 + tj,0 + ti + tj....我们需要模拟这一个过程。
用f[x]表示包里装有体积为x的若干物品这个状态是否可达,可以知道f[0]必然为1,于是,我们依次拿出物品来试装j-> 1 to n,从i:[0,V]这个区间看f数组的值,如果f[i]不为0,说明i这个题解可达,那么i + tj也是可达的了。
for (int j = 1; j <= n; ++j)
{
for (int i = V; i >= 0; --i)
{
if (f[i])
f[i + t[j]) = 1;
}
}
解释一下为什么是for (int i = V; i >= 0; --i)而不是0->V。
我们看个例子我只有一个物品,题解是3,包的大小为10,如果我是0->V循环,那么在检查0状态的时候,3状态被改为1,而循环继续,到3状态,6变成了1,明显体积6是不可能的,所以V->0这种循环方式保证了物品唯一的。
但是我们的题目要求是求最大值,修改一下代码就行了。
for (int j = 1; j <= n; ++j)
{
for (int i = V; i >= 0; --i)
{
if (f[i])
f[i + t[j]) = max(f[i + t[j]], f[i] + v[j]);
}
}
最后只要扫一遍f数组选出最大的就行了,注意减1.
复杂度为O(Vn),比2^n复杂度小多了......
于是,我们再来看看另一个问题,就是......我的n个物品可以随便取,没有个数限制......
这个就是完全背包问题.....我想,进过上面的学习这个已经不是问题。
就是把for (int i = V; i >= 0; --i)改成for (int i = 1; i <= n; ++i),这样就保证了一个物品能取无限次了......
最后一个问题,多重背包问题。
很简单,还是n个物品,这次每个物品既不是1个也不是无限个了,而是有限制的若干个......
如果你能想出对于n个物品,每个有ci个,视为ci个物品,需要做∑ci个物品的背包了,那么不错,学以致用。
但是,往往出现一种不理想的情况,就是n很小,但是ci很大......于是有O(n*∑ci*V)的高复杂度。
于是我们需要优化一下,这就出来多重背包。
对于ci,我们需要知道一点,就是这是相同物品,加入我们单单对ci个物品做01背包,那么过程中肯定有很多冗余判断,假设物品体积为ti,那么ti这个状态会最早可达而且每次都要判断,显然没有必要的。
于是,我们可以对ci进行分解,分解为1,2,4,8.....(2^0, 2^1, 2^2, 2^3,....)这样的数列,不难得出,1,2,4,8....这样数列进行01背包也能得出所有情况......
(13 = 1 + 2 + 4 + 6)
于是,我们ci变为log,复杂度也变为了O(n*log∑ci*V)
送上POJ关于多重背包的一道题,传送门
#include
#include
#include
using namespace std;
int map[200000];
int a[7];
int fa[7][20001];
int main()
{
int n = 1;
while (cin >> a[1] >> a[2] >> a[3] >> a[4] >> a[5] >> a[6])
{
int flag = 0;
memset(map, 0, sizeof(map));
memset(fa, 0, sizeof(fa));
map[0] = 1;
if (a[1] == 0 && a[2] == 0 && a[3] == 0 && a[4] == 0 && a[5] == 0 && a[6] == 0)
break;
int sum = a[1] * 1 + a[2] * 2 + a[3] * 3 + a[4] * 4 + a[5] * 5 + a[6] * 6;
int mid = sum / 2;
if (sum % 2 == 0)
{
for (int i = 1; i <= 6; ++i)
{
int t = 0;
int x = 1;
int tmp = a[i];
while (tmp)
{
if (x < tmp)
{
fa[i][++t] = x;
tmp -= x;
x *= 2;
}
else
{
fa[i][++t] = tmp;
tmp = 0;
}
}
fa[i][0] = t;
}
for (int i = 1; i <= 6; ++i)
{
for (int j = 1; j <= fa[i][0]; ++j)
{
int tmp = fa[i][j] * i;
for (int k = mid; k >= 0; --k)
{
if (map[k])
map[k + tmp] = 1;
}
}
if (map[mid])
{
flag = 1;
break;
}
}
}
if (flag)
{
printf("Collection #%d:\n", n++);
puts("Can be divided.");
}
else
{
printf("Collection #%d:\n", n++);
puts("Can't be divided.");
}
puts("");
}
return 0;
}
这三个也就是面试常见算法了吧........
P.S. 最近重温《K-on!》,推荐那些在苦逼找工作和考研党听一首歌《Don't say lazy》,很燃。