一些背包题
HDU2546
本题的核心思想基于贪心+01背包,因为当卡内余额小于5时无法购买东西,为了让余额最小,总是会有卡内余额大于等于5块钱时的最大消费+价格最大的菜品这样一个贪心表达式,即开头先把最大值挑出来,然后在01背包的过程结束后减去这个最大值。
这里01背包的作用是确定m-5个单位余额最大可以买到多少价格单位的菜,所以容积上界应该设定为m-5。
UVA624
本题是一个01背包+记录路径的题目,因为需要记录路径,就需要额外添加一个记录路径用的数组。在最后可以通过这个数据进行回溯求解放入的物品。
二维DP数组与滚动数组不同的一点是,无法放入物品时也要继承dp[i-1][j]的结果,否则会出现断档的情况。
HDU2955
The aspiring Roy the Robber has seen a lot of American movies, and knows that the bad guys usually gets caught in the end, often because they become too greedy. He has decided to work in the lucrative business of bank robbery only for a short while, before retiring to a comfortable job at a university.
渴望成功的RR看了不少美国电影,了解到坏人经常在最后落入法网的原因是他们过于贪婪。他从决定从事抢劫到现在只有一小段时间,这是在他从大学的某个轻松的职位上退休后的想法。
For a few months now, Roy has been assessing the security of various banks and the amount of cash they hold. He wants to make a calculated risk, and grab as much money as possible.
近期几个月,R已经入侵了许多银行的安全系统得到了这些银行拥有的资金数目,他想计算出风险系数,并且还要得到尽可能多的钱。
His mother, Ola, has decided upon a tolerable probability of getting caught. She feels that he is safe enough if the banks he robs together give a probability less than this.
他母亲,O,已经定好了一个可容忍的被抓概率。如果抢一些银行的被抓总概率小于这个数,就认为这个行动是安全的。
首先这个题的与众不同之处是在于独立事件的概率的计算是乘法计算,与一般的加法背包不同,其次是这里两个数据中代表“容积”的变量是实数变量,实数是不能作下标的,如果用map也不知道循环的步长是多少,所以需要一些变动。
背包问题只能求解最大值(最小值就是什么也不装),按一般想法这里是求出在可容忍被抓概率下的最大钱数,因为上述原因这个方案是不可行的,所以换个思路,求解一定范围钱数下的最大不被抓概率。被抓和不被抓是对立事件,所以直接用1-p就能求出不被抓概率,然后利用背包求出0~所有银行钱数总和的最大不被抓概率,最后从大到小判断一下它们对应的最大不被抓概率与安全概率的关系,输出答案即可。
POJ2184
本题的难点在于负数处理上,因为最大和可能是一个负数与一个大正数的和,而两个数据维度都可能是那个负数,所以一般的模板不能够处理这个题目,使用map很可能会超时。
可以看到本题最大数据总和为[-100000,100000],这也就是说可以把区间总体移到[0,200000]间,这可以通过把零点设定为100000实现,小于1e5的就是负数,大于1e5的就是正数。
这样就可以利用数组存储了,但是还有一个问题:滚动数组优化后容积一般都是从大到小循环,但是这里出现了一个减负数的问题,这样就变成了优先访问大容积,所以对于这个问题可以通过从小到大循环来解决(可能直接把负数处理变成正数亦可)。
必须注意的是负数的边界上限为2e5+it[i],或者在初始化的时候多初始化一点,否则会出现j-it[i]>2e5,访问到未初始化部分的情况
HDU2639
本题是基于一个01背包模板的基础上添加了求第K大答案的要求。然而单纯的把各个答案都记录下来最后去重排序是不行的,因为这些答案的数量可能会非常大(毕竟数字组合),所以这里让dp数组多加了一维,然后利用dp数组维护这么一个最值序列。
如何维护呢?这里使用了两个临时序列A,B,其中A代表前一轮中第i大的值,现状态为取出第i个物品(dp[i-1][j-c[i]][k]),B代表前一轮中第i大的值,不取出现在第i个物品(dp[i-1][j][k]),然后将两个序列按照单减的顺序合并即可。
//#include<pch.h>
#include <iostream>
#include <cstdio>
//#include <bits/stdc++.h>
#include <queue>
#include <map>
#include <algorithm>
#include <stack>
#include <iomanip>
#include <cstring>
#include <cmath>
//#define endl '\n'
#define DETERMINATION main
#define For(a,b,c,d) for(int a=b;a<=c;a+=d)
#pragma GCC optimize(2)
#pragma warning(disable:4996)
#define lldin(a) scanf("%lld", &a)
#define println(a) printf("%lld\n", a)
#define print(a) printf("%lld ", a)
#define reset(a, b) memset(a, b, sizeof(a))
#define debug std::cout<<"procedures above are available"<<"\n";
#define BigInteger __int128
using namespace std;
const long long INF = 2147483647;
const double PI = acos(-1);
typedef long long ll;
typedef unsigned long long ull;
typedef long double ld;
const int mod = 1e9 + 7;
//template<typename T>
//inline BigInteger nextBigInteger()
//{
// BigInteger tmp = 0, si = 1;char c; c = getchar();
// while (!isdigit(c))
//{if (c == '-')si = -1;c = getchar();}
// while (isdigit(c))
// {tmp = tmp * 10 + c - '0';c = getchar();}
// return si * tmp;
//}
//std::ostream& operator<<(std::ostream& os, __int128 T)
//{
// if (T<0) os<<"-";if (T>=10 ) os<<T/10;if (T<=-10) os<<(-(T/10));
// return os<<( (int) (T%10) >0 ? (int) (T%10) : -(int) (T%10) ) ;
//}
//void output(BigInteger x)
//{
// if (x < 0)
// {x = -x;putchar('-');}
// if (x > 9) output(x / 10);
// putchar(x % 10 + '0');
// }
/**Operation Overlord 1944.6.6 Daybreak**/
/**Last Remote**/
ll dp[1500][50];
ll value[1500], volume[1500];
ll queueA[1500], queueB[1500];
int DETERMINATION()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0), std::cout.tie(0);
ll t;
cin >> t;
for (int y = 1; y <= t; y++)
{
ll n, v, k;
cin >> n >> v >> k;
for (int i = 1; i <= n; i++)
cin >> value[i];
for (int i = 1; i <= n; i++)
cin >> volume[i];
reset(dp, 0);
reset(queueA, 0);
reset(queueB, 0);
for (int i = 1; i <= n; i++)
{
for (int j = v; j >= volume[i]; j--)
{
for (int z = 1; z <= k; z++)
{
queueA[z] = dp[j - volume[i]][z] + value[i];
queueB[z] = dp[j][z];
}
ll limit1 = unique(queueA + 1, queueA + k + 1) - queueA-1;
ll limit2 = unique(queueB + 1, queueB + k + 1) - queueB-1;
queueA[limit1+1] = -1;
queueB[limit2+1] = -1;
ll pt1 = 1, pt2 = 1, pt3 = 1;
while (pt3 <= k && (queueA[pt1]!=-1 || queueB[pt2]!=-1))
{
if (queueA[pt1] > queueB[pt2])
{
dp[j][pt3] = queueA[pt1];
pt1++;
}
else
{
dp[j][pt3] = queueB[pt2];
pt2++;
}
if (dp[j][pt3] != dp[j][pt3 - 1])
pt3++;
}
}
}
cout << dp[v][k] << endl;
}
return 0;
}
POJ2923
本题是状态压缩枚举与01背包的综合应用。
因为全题最多只有10个物品可供选择,所以直接用状态压缩枚举即可。
第一个要点在于确认枚举的是否可以一次拿走,也就是说不能超出两辆车总载重,或者无法拆分出适配两辆车的两部分。这里是通过类01背包(无状态选择)标记那些数字组合(两两重量相加,三三相加等等)作为第一辆车承载量,比如说a1,a2,a3,a4四件物品,用01背包可以测出a1+a2,a3+a4,a2+a4,a1+a2+a3....如此就把物品拆分方案确定了,即总重量减去第一辆车承载量就是第二辆车承载量,如此就可以一一判断。
第二个要点就是用01背包选取各种方案,这里的01背包容量上限是物品数量长度的二进制串全为1时对应的十进制形式,因为位运算的减法不好表示(用异或做减法的情况必须是知道某几位是1),所以这里把状态方程平移一个ci单位,用按位或代替加法,既是dp(j|ci)=min(dp(j|ci),dp(j)+1),但是物品拿取方案之间不能有交集,也就是说不能重复拿取一个物品(001,011即为重复),所以还得判断当前容量与拿取方案是否存在交集.
值得注意的是,因为是求最小值,所以需要把初始状态设定为INF,另外本题要求背包的中间状态必须是装满物品后的,也就是说不能有盈余空间,所以当前一个状态是未到达过的情况时,不应进行计算。
//#include<pch.h>
#include <iostream>
#include <cstdio>
//#include <bits/stdc++.h>
#include <queue>
#include <map>
#include <algorithm>
#include <stack>
#include <iomanip>
#include <cstring>
#include <cmath>
//#define endl '\n'
#define DETERMINATION main
#define For(a,b,c,d) for(int a=b;a<=c;a+=d)
#pragma GCC optimize(2)
#pragma warning(disable:4996)
#define lldin(a) scanf("%lld", &a)
#define println(a) printf("%lld\n", a)
#define print(a) printf("%lld ", a)
#define reset(a, b) memset(a, b, sizeof(a))
#define debug std::cout<<"procedures above are available"<<"\n";
#define BigInteger __int128
using namespace std;
const long long INF = 2147483647;
const double PI = acos(-1);
typedef long long ll;
typedef unsigned long long ull;
typedef long double ld;
const int mod = 1e9 + 7;
//template<typename T>
//inline BigInteger nextBigInteger()
//{
// BigInteger tmp = 0, si = 1;char c; c = getchar();
// while (!isdigit(c))
//{if (c == '-')si = -1;c = getchar();}
// while (isdigit(c))
// {tmp = tmp * 10 + c - '0';c = getchar();}
// return si * tmp;
//}
//std::ostream& operator<<(std::ostream& os, __int128 T)
//{
// if (T<0) os<<"-";if (T>=10 ) os<<T/10;if (T<=-10) os<<(-(T/10));
// return os<<( (int) (T%10) >0 ? (int) (T%10) : -(int) (T%10) ) ;
//}
//void output(BigInteger x)
//{
// if (x < 0)
// {x = -x;putchar('-');}
// if (x > 9) output(x / 10);
// putchar(x % 10 + '0');
// }
/**Operation Overlord 1944.6.6 Daybreak**/
/**Last Remote**/
ll arr[592];
ll dp[5002],st[12312];
bool vis[5000];
bool check(ll current,ll v1,ll v2,ll limit)
{
ll sum = 0;
reset(vis, false);
vis[0] = true;
for (int i = 1,k=1; i <=current; i<<=1,k++)
{
if ((i¤t))
{
sum += arr[k];
for (int j = v1; j >= arr[k]; j--)
{
if (vis[j - arr[k]])
vis[j] = true;
}
}
}
//if (current == 7)
//cout << sum << endl;
if (sum > v1 + v2)
return false;
for (int i = 0; i <=v1; i++)
{
if (vis[i] && (sum - i <= v2))
return true;
}
return false;
}
int DETERMINATION()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0), std::cout.tie(0);
ll t;
cin >> t;
for (int y = 1; y <= t; y++)
{
ll n, c1, c2;
cin >> n >> c1 >> c2;
for (int i = 1; i <= n; i++)
cin >> arr[i];
ll limit = (1 << n) - 1;
for (int i = 0; i <=limit; i++)
dp[i] = INF;
ll cnt = 0;
for (int i = 1; i <= limit; i++)
{
if (check(i, c1, c2, n))
{
st[++cnt] = i;
//bitset<4>bt(i);
//cout << bt << endl;
}
}
dp[0] = 0;
for (int i = 1; i <= cnt; i++)
{
for (int j = limit; j >= 0; j--)
{
if (dp[j] ==INF)
continue;
else
{
if ((j&st[i])== 0)
dp[j | st[i]] = min(dp[j | st[i]], dp[j] + 1);
}
}
}
cout << "Scenario #" << y << ":" << endl;
cout << dp[limit] << endl;
if (y != t)
cout << endl;
}
return 0;
}
HDU3466
本题属于贪心+01背包问题。
首先已知两个物品的价格,价值,购买限定财产数额,如何确定他们的购买顺序使得用同样的钱就能买到价值更大的物品?
比如A:5 10 10 B:5 5 5这两个物品,你有10块钱,如果先买A物品,接下来就可以买B物品,但是反过来就不成立了。这是什么原因呢?是因为买完B物品之后的剩余财产达不到购买所需的阈值,所以无法购买。用一个表达式说明就是,p1+q2<p2+q1,即连续购买A,B的成本小于连续购买B,A的成本,移项可得p1-q1<p2-q2,两边同乘-1可得q1-p1>q2-p2.
由上述结论可以得出是依据这两个式子进行排序,凭此确定购买顺序。但是01背包滚动数组优化后的“购买顺序”与其二维数组模式下的购买顺序正好相反,即先买的一定在最后,为什么这么说呢?这是枚举容量的循环:for j=V to j=volume[i],可以看到当j=volume[i],也就是这个循环的最后一个有效值时,放入第i个物品的初始已用背包容量才是0,这也就是所谓向空背包放物品的一个具体体现。
所以需要把上面推出的两个式子的不等号取相反方向,由此才能得出正确答案(如果你用滚动数组优化的话就需要这么做)。
//#include<pch.h>
#include <iostream>
#include <cstdio>
#include <bits/stdc++.h>
#include <queue>
#include <map>
#include <algorithm>
#include <stack>
#include <iomanip>
#include <cstring>
#include <cmath>
//#define endl '\n'
#define DETERMINATION main
#define For(a,b,c,d) for(int a=b;a<=c;a+=d)
#pragma GCC optimize(2)
#pragma warning(disable:4996)
#define lldin(a) scanf("%lld", &a)
#define println(a) printf("%lld\n", a)
#define print(a) printf("%lld ", a)
#define reset(a, b) memset(a, b, sizeof(a))
#define debug std::cout<<"procedures above are available"<<"\n";
#define BigInteger __int128
using namespace std;
const long long INF = 2147483647;
const double PI = acos(-1);
typedef long long ll;
typedef unsigned long long ull;
typedef long double ld;
const int mod = 1e9 + 7;
//template<typename T>
//inline BigInteger nextBigInteger()
//{
// BigInteger tmp = 0, si = 1;char c; c = getchar();
// while (!isdigit(c))
//{if (c == '-')si = -1;c = getchar();}
// while (isdigit(c))
// {tmp = tmp * 10 + c - '0';c = getchar();}
// return si * tmp;
//}
//std::ostream& operator<<(std::ostream& os, __int128 T)
//{
// if (T<0) os<<"-";if (T>=10 ) os<<T/10;if (T<=-10) os<<(-(T/10));
// return os<<( (int) (T%10) >0 ? (int) (T%10) : -(int) (T%10) ) ;
//}
//void output(BigInteger x)
//{
// if (x < 0)
// {x = -x;putchar('-');}
// if (x > 9) output(x / 10);
// putchar(x % 10 + '0');
// }
/**Operation Overlord 1944.6.6 Daybreak**/
/**Last Remote**/
struct node
{
ll p, q, v;
}nodes[5400];
ll dp[60000];
bool cmp(node a, node b)
{
return (a.q - a.p)< (b.q - b.p);
}
int DETERMINATION()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0), std::cout.tie(0);
ll n, m;
while (cin >> n >> m)
{
reset(dp, 0);
for (int i = 1; i <= n; i++)
cin >> nodes[i].p >> nodes[i].q >> nodes[i].v;
sort(nodes + 1, nodes + 1 + n, cmp);
for (int i = 1; i <= n; i++)
{
for (int j = m; j >= nodes[i].q; j--)
dp[j] = max(dp[j], dp[j - nodes[i].p] + nodes[i].v);
}
cout << dp[m] << endl;
}
return 0;
}
HDU2126
本题是关于买最多物品的选择数的问题。
单看选择数,就可以知道本题要采取递推(dp[j]=dp[j]+dp[j-v[i]]),然而还需要求出最大物品数,这就需要再加一维,而不能用外部数组来试图标记。为什么不能用呢?因为单看这个动态规划过程与记忆化搜索极度类似,而记忆化搜索中的状态是不允许使用外部变量存储的,因为这样会把一些描述状态的维度割裂。因此这些维度必须合并到一个数组内。
然而还需要确定枚举顺序:可以认为对于每种物品,对于每一种背包容量(放入后的)来说,这个状态是由放入第几个物品后的状态延伸出来的。
ll dp[540][37];
ll arr[567];
int DETERMINATION()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0), std::cout.tie(0);
ll t;
cin >> t;
for (int y = 1; y <= t; y++)
{
ll n, m;
cin >> n >> m;
for (int i = 0; i <= m + 3; i++)
for (int j = 0; j <= n; j++)
dp[i][j] =0;
for (int i = 0; i <= m; i++)
dp[i][0] = 1;
for (int i = 1; i <= n; i++)
cin >> arr[i];
for (int i = 1; i <= n; i++)
{
for (int j = m; j >= arr[i]; j--)
{
for (int k = n; k >= 1; k--)
{
dp[j][k] =dp[j - arr[i]][k - 1]+dp[j][k];
}
}
}
bool sign = false;
for (int i = n; i >= 1; i--)
{
if (dp[m][i] > 0)
{
sign = true;
cout << "You have " << dp[m][i] << " selection(s) to buy with " << i << " kind(s) of souvenirs." << endl;
break;
}
}
if (sign == false)
cout << "Sorry, you can't buy anything." << endl;
}
return 0;
}
UVA147 dollars
本题与一般的完全背包无异,但是必须注意的是浮点数乘一个数转换成整数时一定要注意精度误差:即(n*100)+0.5。
//#include<pch.h>
#include <iostream>
#include <cstdio>
#include <bits/stdc++.h>
#include <queue>
#include <map>
#include <algorithm>
#include <stack>
#include <iomanip>
#include <cstring>
#include <cmath>
//#define endl '\n'
#define DETERMINATION main
#define For(a,b,c,d) for(int a=b;a<=c;a+=d)
#pragma GCC optimize(2)
#pragma warning(disable:4996)
#define lldin(a) scanf("%lld", &a)
#define println(a) printf("%lld\n", a)
#define print(a) printf("%lld ", a)
#define reset(a, b) memset(a, b, sizeof(a))
#define debug std::cout<<"procedures above are available"<<"\n";
#define BigInteger __int128
using namespace std;
const long long INF = 2147483647;
const double PI = acos(-1);
typedef long long ll;
typedef unsigned long long ull;
typedef long double ld;
const int mod = 1e9 + 7;
//template<typename T>
//inline BigInteger nextBigInteger()
//{
// BigInteger tmp = 0, si = 1;char c; c = getchar();
// while (!isdigit(c))
//{if (c == '-')si = -1;c = getchar();}
// while (isdigit(c))
// {tmp = tmp * 10 + c - '0';c = getchar();}
// return si * tmp;
//}
//std::ostream& operator<<(std::ostream& os, __int128 T)
//{
// if (T<0) os<<"-";if (T>=10 ) os<<T/10;if (T<=-10) os<<(-(T/10));
// return os<<( (int) (T%10) >0 ? (int) (T%10) : -(int) (T%10) ) ;
//}
//void output(BigInteger x)
//{
// if (x < 0)
// {x = -x;putchar('-');}
// if (x > 9) output(x / 10);
// putchar(x % 10 + '0');
// }
/**Operation Overlord 1944.6.6 Daybreak**/
/**Last Remote**/
ll arr[13] = { 0, 10000,5000,2000,1000,500,200,100,50,20,10,5 };
ll dp[40000];
int DETERMINATION()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0), std::cout.tie(0);
double n;
while (cin >> n)
{
if (n == 0)
break;
ll m = (ll)((n * 100)+0.5);
reset(dp, 0);
dp[0] = 1;
for (int i = 1; i <= 11; i++)
{
for (int j = arr[i]; j <= m; j++)
dp[j] = dp[j] + dp[j - arr[i]];
}
cout << fixed << setprecision(2) << setw(6) << setfill(' ') << n;
cout << setw(17) << setfill(' ') << dp[m] << endl;
}
return 0;
}

浙公网安备 33010602011771号