背包九讲
2018-05-06 13:35:32
感谢: https://blog.csdn.net/ling_du/article/details/41594767
一讲:01背包问题
问题描述:给定一个袋子的容积是v,给定n种物品,每种物品有两种属性,价值val和体积vol,每种物品只有一个,问如何选择可以使得得到的价值最大
我们知道每个物品只有选或不选两种状态,那么为什么不可以遍历每种状态呢,此时,如果有n种物品,那么就需要求2^n种情况的值
那么01背包的时间复杂度是多少呢?其实01背包是一种DP 时间复杂度分析完再说
思路:对于每一个物品 都有选或不选两种状态 设置数组dp[i][v]表示前i件物品恰放进一个体积为v的背包得到得最大价值 第i件物品放或不放得到的价值为什么不能直接由前i-1件物品放或不放得到呢 为什么还与体积有关 因为可能我们现在虽然价值得到了最大 但是没有节约体积 以至于后面的价值大的物品 不能放进去 据失去了最优解 于是记录一下体积 就会防止发生这种情况
转移方程如下: 当第i件物品放入体积为v的背包中是 证明前i-1件物品放在了体积为v-vol[i]中 于是dp[i][v]=dp[i-1][v-vol[i]] 当第i件物品不放进体积为v的背包中时 dp[i][v] = dp[i-1][v] 求最大值
伪代码:
for(int i=1; i<=n; i++) { for(int v=1; v<V; v++) { dp[i][v] = max(dp[i-1][v-vol[i]]+val[i] , dp[i-1][v]);放或不放 } }
此时的时间复杂度为O(n*v) 暴力的时间是指数级的 这个时间是正比级的 在n大的情况下省很多时间
优化:时间上做不到优化 空间上可以有优化 此时的空间复杂的也是O(n*v),但是可以优化到O(v) 我们看上面的转移方程
dp[i]求的时候 在二维数组的第一维上只与i-1有关 于是我们可以省掉第一维 只开dp[v] 但是由于与i-1有关 于是我们采取反向遍历就好
例题:以 Just another Robbery LightOJ - 1079为例 多了一个难点就是这个是概率的01背包 其实也是写到这个题才想要看一下背包九讲的 兴致勃勃告诉队友我要去学习概率DP了 期待晚上告诉他们我看完了背包九讲时他们的态度 嘻嘻
题意:小花要去强银行 自己危险值的上限是p 有n家银行 每家银行有对应的价值和危险值 问最多可以抢多少钱
代码如下:
#include<stdio.h> #include<iostream> using namespace std; int t; int n; double P; int v[110]; double p[110]; double dp[105][105*105]; int sum; void init() { sum = 0; } void input() { for(int i=1; i<=n; i++) { scanf("%d%lf" , &v[i] , &p[i]); sum += v[i]; } } void solve() { /* dp[i][j] 记录的是抢了i个银行 得到了j元钱 被抓的概率 */ for(int i=1; i<=sum; i++) dp[0][i] = -1; //抢了0个银行 得到j元钱是不可能的 j>0 dp[0][0] = 0; //抢了0个银行 得到0元钱 被抓的概率是0 for(int i=1; i<=n; i++) { for(int j=0; j<=sum; j++) { /* 因为dp初始值都为0 所以只有在i-1=0的时候才会出现小于-0.5 / <0的情况 */ if(j<v[i] || dp[i-1][j-v[i]]<=-1) //第i个银行强不了(j<v[i]) 或者虽然第i个银行可以抢 但是如果抢了他 他前面的九不成立了 也就是虽然可以建二楼 但是你一楼就垮了 所以二楼还是一楼 dp[i][j] = dp[i-1][j]; else if(dp[i-1][j]<0) dp[i][j] = dp[i-1][j-v[i]] + (1-dp[i-1][j-v[i]])*p[i]; //第i个银行可以抢 因为j>v[i] if里对应抢i-1个银行得不到j元钱的情况 此时第i个银行一定得抢 else dp[i][j] = min(dp[i-1][j], dp[i-1][j-v[i]]+(1-dp[i-1][j-v[i]])*p[i]); //前i-1次被抓的概率是dp[i-1][j-v[i]] 于是不被抓的概率就是1-dp[i-1][j-v[i]] 再乘上此时被抓的概率 } } } int main() { scanf("%d" , &t); for(int cas=1; cas<=t; cas++) { scanf("%lf%d" , &P , &n); init(); input(); solve(); int ans = 0; // for(int j=0; j<=n; j++) // { for(int i=0; i<=sum; i++) { if(dp[n][i]>-1 && dp[n][i]<P) ans = i; // printf("%lf..." , dp[j][i]); } // printf("\n"); // } printf("Case %d: %d\n" , cas , ans); } return 0; }