问题七
题意描述
01背包问题的一个变形,在原题的基础上,要求背包的容量刚好占满。
思路
其实只需要改动一下初始化的方法。
在原本的01背包问题中,我们的状态转移方程为
f[i][j] = max(f[i - 1][j], f[i - 1][j - w[i]] + v[i])
我们可以发现,如果不是恰好装满,那么部分状态对我们来说就是无效的。
如果无效状态无法推出有效状态,我们就不必关心无效状态(背包没装满)时候,背包内物体的总价值。
因为背包恰好装满时候我们只关心有效状态,而无效状态又无法推出有效状态。
也就可以把所有无效状态时背包的总价值都设置为负无穷。
然后判断最终结果,如果是负无穷,说明无法装满。
代码
代码实现时,我只有一维数组表示状态,这是01背包问题中优化空间复杂度的方法。
因为在状态转移时,可以发现每次的状态转移只与两个状态有关,所以可以优化掉第一维,也就是表示哪件物体。
对应的,需要将第二个循环改为倒序,这样就可以保证每次只从过去的状态转移过来;如果是正序,就变为了完全背包了。
#include <iostream>
using namespace std;
#define max(N1,N2) N1>N2?N1:N2
#define INF 0x80000000
int main()
{
int V, N;
while (cin >> V >> N)
{
int v[1000], w[1000];
int f[10000] ;
for (int i = 0; i < 10000; i++)
{
f[i] = INF;
}
f[0] = 0;
for (int i = 1; i <= N; i++)
{
cin >> v[i] >> w[i];
}
for (int i = 1; i <= N; i++)
{
for (int j = V; j >= v[i]; j--)
{
f[j] = max(f[j], f[j - v[i]] + w[i]);
if (f[j] < 0)
f[j] = INF;
}
}
if (f[V] > 0)
{
cout << f[V] << endl;
}
else
{
cout << "error" << endl;
}
}
return 0;
}
问题九
题意描述
经典问题,最长不下降子序列。
思路
(1)最优子结构
如果一个数列A是数列B的最长上升子序列,那么相应的在数列A和数列B中去掉数列A中的某个数字之后,A剩下的序列肯定也是B剩下序列中最长的子序列 .
(2)重叠子问题
将上面的数列A和数列B去掉某一个数之后与未去之前具有相同的问题性质,也就是子问题。
复杂度为 O(n^2) 的算法就是在每个数的初始情况下的最大上升子序列长度为1。
讨论复杂度为 O(nlogn) 的算法:
维护一个数组 tail , 其中 taili存储长度为 i 的所有上升子序列中末尾最小的元素。在更新过程中,我们使用二分搜索来确定一个元素应当放置在 tail 中的位置。
我们还是从左向右来枚举,对于每个元素 ai,在 tail 中找到第一个大于 ai 的位置。
- 如果找到这样的位置,即表示之前有一个序列的末尾可以被替换,所以修改其 tail 值为新 ai
- 如果没有找到,说明 ai 比之前所有数字都大,那么把 ai 追加到 tail 末尾,最长的上升序列长度成功加 1
代码
#include<iostream>
using namespace std;
const int maxN = 105;
int n;
int arr[maxN];
//使用O(n^2)的方法
void getLIS1(){
int f[maxN];//f[i]表示 arr[i]的最长不下降子序列的长度
fill(f,f+maxN,1);//初始化为1
int res = 0;
for(int i =1;i< n;i++){
for(int j = 0;j<i;j++){
if(arr[i] >= arr[j]){//如果当前的数不小于 arr[j]
f[i] = max(f[i],f[j]+1);
}
}
res = max(res,f[i]);
}
cout << res <<"\n";
}
//使用O(nlogn)的方法
void getLIS2(){
int tail[maxN ];//tail[i]表示长度为i的LNDS中结尾的元素
fill(tail,tail+maxN,0);
int cnt = 1;
tail[cnt] = arr[0];//第一个长度为1的最长不下降子序列的结尾元素是arr[0]
for(int i = 1;i< n;i++){
if(arr[i] >= tail[cnt]){
tail[++cnt] = arr[i];
}
else{//二分找出最小的
int idx= lower_bound(tail+1,tail+cnt+1,arr[i]) - tail;
tail[idx] = arr[i];//换掉这个元素
}
}
cout << cnt<<"\n";
}
//计算最长不下降子序列
int main(){
cin >> n;
for(int i = 0;i< n;i++){
cin >> arr[i];
}
getLIS1();
getLIS2();
}