连续子数组的最大和(贪心、动态规划)(题目链接)
题目描述:
输入一个 非空 整型数组,数组里的数可能为正,也可能为负。
数组中一个或连续的多个整数组成一个子数组。
求所有子数组的和的最大值。
要求时间复杂度为O(n)。
解题思路:
最粗暴的方法就是都遍历一遍
class Solution {
public:
int maxSubArray(vector<int>& nums) {
const int N = nums.size();
int maxn = INT_MIN;
for(int i=0;i<N;i++){
for(int j=i;j<N;j++){
int sum=0;
for(int k=i;k<=j;k++){
sum+=nums[k];
}
maxn = max(maxn,sum);
}
}
return maxn;
}
};
在AcWing上,这样的写法也能AC,但是显然太暴力了。所以我就想到了下面这种方法。用一个二维数组来保存各种结果,比如f[i][j]表示下标i到j之间的和。首先算出0-1,0-2,0-3,…,0-N的结果,保存在数组的第0行中,之后遍历第i行的每一列时,只要依次减去第i-1个数就行了。
class Solution {
public:
int maxSubArray(vector<int>& nums) {
const int N = nums.size();
int f[N][N],sum=0,maxn = nums[0];
for(int j=0;j<N;j++){
sum+=nums[j];
f[0][j]=sum;
maxn = max(maxn,f[0][j]);
}
for(int i=1;i<N;i++){
for(int j=i;j<N;j++){
f[i][j]=f[i-1][j]-nums[i-1];
maxn = max(maxn,f[i][j]);
}
}
return maxn;
}
};
写完之后当然也能AC,但是显然不满足题目时间复杂度为O(n)的要求。后来看了别人的代码,就感觉自己的代码真“复杂”,感觉别人的代码真神奇,让我有点似懂非懂,好好捋捋。
class Solution {
public:
int maxSubArray(vector<int>& nums) {
int s=0,l=INT_MIN;
for(auto num:nums){
//如果之前的和为负数,就摒弃之前的,因为都是负数了,要过来只会成为累赘
if(s<0) s=0;
//加上当前这个数
s+=num;
//如果当前结果大于目前的最大值,则替换最大值。
l = max(l,s);
}
return l;
}
};
Q:最大值的结果能考虑到都是负数的情况么?
A:程序只会在之前结果为负数时将s清零,而当前数为负数的话就会在l = max(l,s);
更新最大值,所以负数也是可以被考虑成最大值的。
Q:为什么这样就可以计算出连续子数组的最大值?
A:在每一次循环中我们可以得到以i-1为结尾的相邻数的和的最大值s、当前数num,那么前i个数的连续子数组的最大值就是目前的最大值l、s+num(s>0时) 和num(s<0时) 中的最大值。
Q:为什么在每一次循环中我们可以得到以i-1为结尾的相邻数的和的最大值?
A:这个过程是从头开始的,每次累加的结果小于0,这一部分的数就会被立即摒弃。可以理解为这一部分的数“弊大于利”,因为其中abs(负数的和)大于abs(正数的和)。只有当这一部分数的和大于0时,才会被留下来。
总结:
还是讲不太清楚,可以自己在草稿纸上模拟一遍运算过程,说不定就能看懂了。
圆圈中最后剩下的数字(模拟、链表、动态规划、递推)(题目链接)
题目描述:
0, 1, …, n-1这n个数字(n>0)排成一个圆圈,从数字0开始每次从这个圆圈里删除第m个数字。
求出这个圆圈里剩下的最后一个数字。
解题思路:
似乎叫约瑟夫环,每删除一个数字后,从删除的数开始重新编号,可以发现新编号与旧编号的关系是:旧=(新+m)%n
。
用递推的方法来做这道题,lastRemaining(n,m)代表共有n个数,返回其中第m个数的编号。而递推的终点就是n=1。根据新旧编号的关系,就可以得出最后一个数的原始编号。
代码:
class Solution {
public:
int lastRemaining(int n, int m){
if(n==1) return 0;
return (lastRemaining(n-1,m)+m)%n;
}
};
数组组合(动态规划、01背包问题)(题目链接)
题目描述:
给定N个正整数A1,A2,…,AN,从中选出若干个数,使它们的和为M,求有多少种选择方案。
解题思路:
就是简单版的背包问题求方案数,用数组f[i][j]表示前i个数,总和为j的方案数。那么f[i][j]就等于不算上当前这个数的情况(f[i-1][j]) 加上算上当前这个数的情况(f[i-1][j-v]) 的方案数和。
代码1:
#include<iostream>
#include<cstring>
#include<string>
#include<algorithm>
#include<vector>
#include<map>
#include<queue>
#include<cstdio>
#include<cmath>
using namespace std;
const int N = 10010;
int n,m,f[N][N],v;
int main(){
// freopen("1.txt","r",stdin);
cin>>n>>m;
f[0][0]=1;
for(int i=1;i<=n;i++){
cin>>v;
for(int j=0;j<=m;j++){
if(j<v) f[i][j]=f[i-1][j];
else f[i][j]=f[i-1][j]+f[i-1][j-v];
}
}
cout<<f[n][m]<<endl;
return 0;
}
代码2(改进版)
原来的代码有点多余
#include<iostream>
#include<cstring>
#include<string>
#include<algorithm>
#include<vector>
#include<map>
#include<queue>
#include<cstdio>
#include<cmath>
using namespace std;
const int N = 10010;
int n,m,f[N],v;
int main(){
// freopen("1.txt","r",stdin);
cin>>n>>m;
f[0]=1;
for(int i=1;i<=n;i++){
cin>>v;
for(int j=m;j>=v;j--){
f[j]=f[j]+f[j-v];
}
}
cout<<f[m]<<endl;
return 0;
}
自然数拆分(动态规划、完全背包问题)(题目链接)
题目描述:
给定一个自然数N,要求把N拆分成若干个正整数相加的形式,参与加法运算的数可以重复。
解题思路:
即完全背包问题求方案数,用数组f[j]保存和为j的方案数,再结合完全背包问题的思想。
代码:
#include<iostream>
#include<cstring>
#include<string>
#include<algorithm>
#include<vector>
#include<map>
#include<queue>
#include<cstdio>
#include<cmath>
using namespace std;
const int N = 4010;
int n;
long long f[N];
int main(){
// freopen("1.txt","r",stdin);
cin>>n;
f[0]=1;
for(int i=1;i<n;i++){
for(int j=i;j<=n;j++){
f[j] = (f[j-i]+f[j])%2147483648;
}
}
cout<<f[n]<<endl;
return 0;
}
采药(01背包问题)(题目链接)
题目描述:
有m种药,每种药有两个参数:需要花费的时间(t)和价值(w),问在T时间内,能采得草药的最大价值是多少?
解题思路:
简单的01背包问题,只是把体积变成了时间。
代码:
#include<iostream>
#include<cstring>
#include<string>
#include<algorithm>
#include<vector>
#include<map>
#include<queue>
#include<cstdio>
#include<cmath>
using namespace std;
const int N = 1010;
int f[N],T,m,t,w;
int main(){
// freopen("1.txt","r",stdin);
cin>>T>>m;
for(int i=0;i<m;i++){
cin>>t>>w;
for(int j=T;j>=t;j--){
f[j]=max(f[j],f[j-t]+w);
}
}
cout<<f[T]<<endl;
return 0;
}
开心的金明(01背包问题)(题目链接)
题目描述:
金明今天很开心,家里购置的新房就要领钥匙了,新房里有一间他自己专用的很宽敞的房间。
更让他高兴的是,妈妈昨天对他说:“你的房间需要购买哪些物品,怎么布置,你说了算,只要不超过N元钱就行”。
今天一早金明就开始做预算,但是他想买的东西太多了,肯定会超过妈妈限定的N元。
于是,他把每件物品规定了一个重要度,分为5等:用整数1~5表示,第5等最重要。
他还从因特网上查到了每件物品的价格(都是整数元)。
他希望在不超过N元(可以等于N元)的前提下,使每件物品的价格与重要度的乘积的总和最大。
设第j件物品的价格为v[j],重要度为w[j],共选中了k件物品,编号依次为j1,j2,…,jk,则所求的总和为: v[j1]∗w[j1]+v[j2]∗w[j2]+…+v[jk]∗w[jk]
请你帮助金明设计一个满足要求的购物单。
解题思路:
相对于之前的01背包问题,只是在比较值的时候有点不一样。根据题目的要求可知状态转移方程为:f[j]=max(f[j],f[j-v]+p*v);
代码:
#include<iostream>
#include<cstring>
#include<string>
#include<algorithm>
#include<vector>
#include<map>
#include<queue>
#include<cstdio>
#include<cmath>
using namespace std;
const int N = 30010;
int f[N],n,m,v,p;
int main(){
// freopen("1.txt","r",stdin);
cin>>n>>m;
for(int i=0;i<m;i++){
cin>>v>>p;
for(int j=n;j>=v;j--){
f[j]=max(f[j],f[j-v]+p*v);
}
}
cout<<f[n]<<endl;
return 0;
}
吃蛋糕(完全背包问题)(题目链接)
题目描述:
惠特利在参加世界上最好的派对:它有着无数的蛋糕!
每个蛋糕都呈具有整数边长(cm)的正方形形状。
派对上有无限多个边长为任意整数的蛋糕。
蛋糕都有相同的高度,所以我们只考虑它们的面积。
惠特利决定吃一个或多个蛋糕,使其总面积恰好为N cm2。
你能帮他计算一下他最少需要吃掉多少个蛋糕吗?
解题思路:
每个蛋糕都可以选择任意多个,所以是典型的完全背包问题。根据题目的思路,我们可以用f[i] 来表示面积为i的情况下的最少蛋糕数。因此最开始的时候就需要把数组的初始状态都赋值为“无穷大”,而f[0]设为0,这样才能保证f[n]保存的结果是面积正好为n的最少蛋糕数。在选择最优解时,比较的就是两种方案的蛋糕数,数量较少的就是最优解。
代码:
#include<iostream>
#include<cstring>
#include<string>
#include<algorithm>
#include<vector>
#include<map>
#include<queue>
#include<cstdio>
#include<cmath>
using namespace std;
const int N = 10010;
int f[N],T,n,k=0;
int main(){
// freopen("1.txt","r",stdin);
cin>>T;
while(T--){
cin>>n;
memset(f,0x3f,sizeof f);
f[0]=0;
for(int i=1;i*i<=n;i++){
for(int j=i*i;j<=n;j++){
f[j]=min(f[j],f[j-i*i]+1);
}
}
cout<<"Case #"<<++k<<": "<<f[n]<<endl;
}
return 0;
}
取硬币(完全背包问题+01背包问题求方案数)(题目链接)
题目描述:
现在有 n1+n2 种面值的硬币,其中前 n1 种为普通币,可以取任意枚,后 n2 种为纪念币,每种最多只能取 1 枚,每种硬币有一个面值,问能用多少种方法拼出 m 的面值?
解题思路:
用数组f[i]保存面值为i的方案数,因此f[i]=f[i]+f[i-a],其中a为当前硬币的面值。注意多重背包中的循环是从前往后,01背包中的循环是从后往前。
代码:
#include<iostream>
#include<cstring>
#include<string>
#include<algorithm>
#include<vector>
#include<map>
#include<queue>
#include<cstdio>
#include<cmath>
using namespace std;
const int N = 100010;
int n1,n2,m,f[N],a;
int main(){
// freopen("1.txt","r",stdin);
cin>>n1>>n2>>m;
f[0]=1;
//多重背包
for(int i=0;i<n1;i++){
cin>>a;
for(int j=a;j<=m;j++){
f[j] = (f[j]+f[j-a])%1000000007;
}
}
//01背包
for(int i=0;i<n2;i++){
cin>>a;
for(int j=m;j>=a;j--){
f[j] = (f[j]+f[j-a])%1000000007;
}
}
cout<<f[m]<<endl;
return 0;
}
打怪(01背包问题)(题目链接)
题目描述:
哥斯拉打败基多拉后觉得意犹未尽,叫来了 n 个怪兽跟他操练。
然而哥斯拉在战胜基多拉后只剩下了 w 个能量单位,所以他并不一定能打败所有怪兽。
哥斯拉有一个基础攻击力 A,还有一个技能攻击力加成 B(释放技能伤害为 A+B)。
每一个怪兽都有两个属性,攻击力 xi 和生命值 yi,如果哥斯拉的最大伤害比该怪兽的攻击力 xi 小, 那么哥斯拉就不能战胜它。
如果战胜它,则会消耗哥斯拉 yi 点能量值。
哥斯拉想知道他最多能打败多少个怪兽。
解题思路:
依旧是选或不选的问题,选的话就是f[j-y]+1,不选的话就是f[j],取最大值就行。
代码:
#include<iostream>
#include<cstring>
#include<string>
#include<algorithm>
#include<vector>
#include<map>
#include<queue>
#include<cstdio>
#include<cmath>
using namespace std;
const int N = 5010;
int n,A,B,w,x,y,f[N];
int main(){
// freopen("1.txt","r",stdin);
cin>>n>>w>>A>>B;
for(int i=0;i<n;i++){
cin>>x>>y;
if(A+B<x) continue;
for(int j=w;j>=y;j--){
f[j] = max(f[j],f[j-y]+1);
}
}
cout<<f[w]<<endl;
return 0;
}
划分大理石(多重背包问题)(题目链接)
题目描述:
有价值分别为1…6的大理石各a[1…6]块,现要将它们分成两部分,使得两部分价值之和相等,问是否可以实现。
其中大理石的总数不超过20000。
解题思路:
问是否能将其分成两部分,也就是说前n块石头能否组成价值为所求价值/2的情况。在之前的题目中,数组f保存的是前i个物品,体积为j的最优解。现在只需要改一改,用数组f来保存前i个物品,价值为j的情况是否存在。f[j]==1表示存在,f[j]==0表示不存在。这样的话,数组f在初始化的时候,除了f[0]=1,其余都要初始化为0。在状态转移的时候,只需要判断选与不选两种情况是否至少有一种情况存在,如果存在,f[i]就为1,反之为0。另外我们需要利用二进制优化来改进代码,否则会超时。在开始的时候,如果总价值为奇数,也可以直接判断出结果为Can’t。
代码:
#include<iostream>
#include<cstring>
#include<string>
#include<algorithm>
#include<vector>
#include<map>
#include<queue>
#include<cstdio>
#include<cmath>
using namespace std;
const int N = 20010;
int f[N],a[6],sum;
vector<int>v;
int main(){
// freopen("1.txt","r",stdin);
while(cin>>a[0]>>a[1]>>a[2]>>a[3]>>a[4]>>a[5]){
if(a[0]==0&&a[1]==0&&a[2]==0&&a[3]==0&&a[4]==0&&a[5]==0) break;
sum=a[0]+a[1]*2+a[2]*3+a[3]*4+a[4]*5+a[5]*6;
if(sum%2!=0){
cout<<"Can't"<<endl;
continue;
}
sum/=2;
memset(f,0,sizeof(f));
for(int i=1;i<=6;i++){
for(int k=1;k<=a[i-1];k++){
v.push_back(k*i);
a[i-1]-=k;
}
if(a[i-1]>0) v.push_back(a[i-1]*i);
}
f[0]=1;
for(auto i:v){
for(int j=sum;j>=i;j--){
if(f[j-i]==1||f[j]==1) f[j]=1;
else f[j]=0;
}
}
if(f[sum]==1) cout<<"Can"<<endl;
else cout<<"Can't"<<endl;
}
return 0;
}
糖果(01背包问题)(题目链接)
题目描述:
由于在维护世界和平的事务中做出巨大贡献,Dzx被赠予糖果公司2010年5月23日当天无限量糖果免费优惠券。
在这一天,Dzx可以从糖果公司的 N 件产品中任意选择若干件带回家享用。
糖果公司的 N 件产品每件都包含数量不同的糖果。
Dzx希望他选择的产品包含的糖果总数是 K 的整数倍,这样他才能平均地将糖果分给帮助他维护世界和平的伙伴们。
当然,在满足这一条件的基础上,糖果总数越多越好。
Dzx最多能带走多少糖果呢?
注意:Dzx只能将糖果公司的产品整件带走。
解题思路:
根据糖果数量对K的余数进行讨论。每件产品有选和不选两种选择,与之前01背包问题有所不同的是,本题要遍历所有的余数,并且在遍历每一个余数的时候,任何位置的数据都用可能被用到,因此数组f要定义为二维数组,不能直接像之前那样用一维数组就能解决。在状态转移公式上也看起来比之前的复杂f[i][j] = max(f[i-1][j],f[i-1][((j+k)-m%k)%k]+m);
,其中f[i-1][j]
表示不选的情况,直接取前i-1个物品,余数为j的最大值;f[i-1][((j+k)-m%k)%k]+m
表示选的情况,比如当j=2,k=7,m=10时,取的情况下所得数量为前i-1个物品,余数为6的值(即(j+k)-m%k)%k
,j+k
是防止j太小,m%k
是防止m太大,最后的%k
是保证最后的结果为k的余数)加上当前产品糖果的数量10。最后的结果就是前n个物品,余数为0的值。
代码:
#include<iostream>
#include<cstring>
#include<string>
#include<algorithm>
#include<vector>
#include<map>
#include<queue>
#include<cstdio>
#include<cmath>
using namespace std;
const int N = 110;
int n,m,k,f[N][N];
int main(){
// freopen("1.txt","r",stdin);
cin>>n>>k;
for(int i=0;i<=n;i++) memset(f[i],-0x3f,sizeof f[i]);
f[0][0]=0;
for(int i=1;i<=n;i++){
cin>>m;
for(int j=0;j<k;j++){
f[i][j] = max(f[i-1][j],f[i-1][((j+k)-m%k)%k]+m);
}
}
cout<<f[n][0]<<endl;
return 0;
}
包子凑数(完全背包问题、数论)(题目链接)
题目描述:
小明几乎每天早晨都会在一家包子铺吃早餐。
他发现这家包子铺有 N 种蒸笼,其中第 i 种蒸笼恰好能放 Ai 个包子。
每种蒸笼都有非常多笼,可以认为是无限笼。
每当有顾客想买 X 个包子,卖包子的大叔就会迅速选出若干笼包子来,使得这若干笼中恰好一共有 X 个包子。
比如一共有 3 种蒸笼,分别能放 3、4 和 5 个包子。
当顾客想买 11 个包子时,大叔就会选 2 笼 3 个的再加 1 笼 5 个的(也可能选出 1 笼 3 个的再加 2 笼 4 个的)。
当然有时包子大叔无论如何也凑不出顾客想买的数量。
比如一共有 3 种蒸笼,分别能放 4、5 和 6 个包子。
而顾客想买 7 个包子时,大叔就凑不出来了。
小明想知道一共有多少种数目是包子大叔凑不出来的。
解题思路:
关键在于怎么判断凑不出的个数是不是有限个? 答案就是所有数的最大公约数是1,则凑不出的数是有限多个的。然后根据完全背包的思想,即前i个物品,能否凑出数量和为j的结果,状态转移方程就是只要f[j]与f[j-a]中有一个是能,则f[j]为能。最后再统计一下不能的数量就行了。
代码:
#include<iostream>
#include<cstring>
#include<string>
#include<algorithm>
#include<vector>
#include<map>
#include<queue>
#include<cstdio>
#include<cmath>
using namespace std;
const int N = 10010;
int n,f[N],a,d=0;
int gcd(int a, int b){
return b ? gcd(b, a % b) : a;
}
int main() {
// freopen("1.txt","r",stdin);
cin>>n;
f[0]=1;
for(int i=0; i<n; i++) {
cin>>a;
d = gcd(d,a);
for(int j=a; j<N; j++) {
if(f[j]==1||f[j-a]==1) f[j]=1;
}
}
if(d!=1) cout<<"INF"<<endl;
else {
int sum=0;
for(int i=1; i<N; i++) {
if(f[i]==0) sum++;
}
cout<<sum<<endl;
}
return 0;
}
倍数问题(背包问题)(题目链接)
题目描述:
众所周知,小葱同学擅长计算,尤其擅长计算一个数是否是另外一个数的倍数。
但小葱只擅长两个数的情况,当有很多个数之后就会比较苦恼。
现在小葱给了你 n 个数,希望你从这 n 个数中找到三个数,使得这三个数的和是 K 的倍数,且这个和最大。
数据保证一定有解。
解题思路:
同样每个数有选或不选两种选择,但是这里多了一重条件,即只能选择三个数。所以我们的数组f要多一维。另外题目要求三个数的和为K的倍数,所以我们只需要考虑其对K的余数。f[i][j][k],表示前i个数,余数为j,是三个数中的第k个数。而根据数据的范围,数组设置的太大了,所以我们同样会减去第一维。这样我们就得考虑循环的方向,使得需要的数据不会被提前覆盖。首先考虑状态转移方程,比如f[j][2] = max(f[((j+k)-(a%k))%k][1]+a,f[j][2]);
表示余数为j,当前数作为3个数中的第2个数的最优解,即选与不选两种情况之中最大的值。其中关于((j+k)-(a%k))%k
的解释参考往上第二题的糖果。现在考虑我们应该先计算当前数作为3个数中的第1个数的最优解还是先计算当前数作为3个数中的第3个数的最优解。当我们计算当前数作为3个数中的第3个数的最优解时,我们需要用到上一轮某个数作为3个数中的第2个数的最优解。当我们计算当前数作为3个数中的第2个数的最优解时,我们需要用到上一轮某个数作为3个数中的第1个数的最优解。因此我们计算f[][3]的时候,f[][2]与f[][1]都还不能被新数据覆盖,所以我们遍历的顺序应该是3->1。另外就是在遍历余数的时候怎样才能不让需要的数据不要被覆盖。答案就是外层循环遍历当前数作为3个数中的第z个数,内层循环遍历余数j(这里的循环方向随意)。这样在计算当前数作为3个数中的第z个数的时候,上一轮某个数作为3个数中的第z-1个数的数据都还未被覆盖。解释能力有限,具体看代码。
代码:
#include<iostream>
#include<cstring>
#include<string>
#include<algorithm>
#include<vector>
#include<map>
#include<queue>
#include<cstdio>
#include<cmath>
using namespace std;
const int N = 1010;
int n,k,a,f[N][4];//f[i][j]表示余数为i,选择j个物品的最大的和
vector<int>v[N];
int main() {
// freopen("1.txt","r",stdin);
memset(f,-0x3f,sizeof f);
f[0][0]=0;
cin>>n>>k;
for(int i=1; i<=n; i++) {
cin>>a;
v[a%k].push_back(a);
}
for(int i=0; i<k; i++) {
sort(v[i].begin(),v[i].end());
reverse(v[i].begin(),v[i].end());
for(int u=0; u<3&&u<v[i].size(); u++) {
a = v[i][u];
for(int z=3;z>0;z--){
for(int j=0;j<k;j++){
f[j][z] = max(f[((j+k)-(a%k))%k][z-1]+a,f[j][z]);
}
}
}
}
cout<<f[0][3]<<endl;
return 0;
}
找更多硬币(背包问题求具体方案)(题目链接)
题目描述:
伊娃喜欢从整个宇宙中收集硬币。
有一天,她去了一家宇宙购物中心购物,结账时可以使用各种硬币付款。
但是,有一个特殊的付款要求:每张帐单,她都必须准确的支付所消费金额。
给定她拥有的所有硬币的面额,请你帮她确定对于给定的金额,她能否找到一些硬币来支付。
解题思路:
首先我们可以将问题简化,先考虑能否准确地支付所消费的金额。这个简单,就是普通的背包问题,用数组f[i][j]表示前i个数,金额总和为j的情况。等于0表示不存在这种情况,等于1表示存在。状态转移方程就是if(f[i-1][j]==1||f[i-1][j-a[i]]==1) f[i][j]=1;
只要选与不选两种情况中有一种情况是1,则当前的情况为1。然后再考虑怎么输出最小的支付面额序列。既然是最小, 所以我们要从最小的数开始输出。而背包问题求具体方案的思想就是从后往前遍历每个数以得出具体方案。所以最开始我们要把所有的数从大到小排序。最后在输出具体方案的时候根据t>=a[i]&&f[i-1][t-a[i]]==1
是否成立来判断当前这个数是否被取。成立代表这个数被取,不成立代表不取这个数。
代码:
#include<iostream>
#include<cstring>
#include<string>
#include<algorithm>
#include<vector>
#include<map>
#include<queue>
#include<cstdio>
#include<cmath>
using namespace std;
const int N = 110;
int n,m,f[10010][N],a[10010];
int main(){
// freopen("1.txt","r",stdin);
cin>>n>>m;
f[0][0]=1;
for(int i=1;i<=n;i++){
cin>>a[i];
}
sort(a+1,a+n+1);
reverse(a+1,a+n+1);
for(int i=1;i<=n;i++){
for(int j=0;j<=m;j++){
if(j<a[i]) f[i][j]=f[i-1][j];
else if(f[i-1][j]==1||f[i-1][j-a[i]]==1) f[i][j]=1;
}
}
if(f[n][m]==1){
int t=m;
for(int i=n;i>=1;i--){
if(t>=a[i]&&f[i-1][t-a[i]]==1){
cout<<a[i]<<" ";
t-=a[i];
}
}
}else cout<<"No Solution"<<endl;
return 0;
}
整数分解(完全背包问题求具体方案)(题目链接)
题目描述:
正整数 N 的 K−P 分解,是将 N 写为 K 个正整数的 P 次幂的和。
请你编写一个程序,给定 N,K,P 的情况下,找到 N 的 K−P 分解。
如果存在 N 的 K−P 分解,则以如下格式输出:
N = n[1]^P + … n[K]^P
其中,n[i] 是第 i 个因子,所有因子必须按照不升序顺序输出。
注意,答案也许不唯一。
例如,169 的 5−2 分解共有 9 种,如 122+42+22+22+12,112+62+22+22+22 等等。
你需要输出各因子之和最大的一种解法。
如果仍不能确定唯一解法,则选择因子序列更大的解法。
我们称序列 {a1,a2,…,aK} 大于序列 {b1,b2,…,bK},当且仅当存在 1≤L≤K,满足当 i<L 时,ai=bi 且 aL>bL。
如果无解,则直接输出 Impossible。
解题思路:
可以把N当做背包的体积,背包的承受重量为K,每个因子代表一个物品,比如因子i代表体积为i^P,价值为i,重量为1的物品,且每个物品可以选任意多次。而我们要做的就是求出物品体积和为N,重量和为K的最大价值。因为最后要输出具体方案,所以数组要设成三维的,f[i][j][z]代表重量为i,前j种物品,体积为z的最大价值。状态转移方程就是f[i][j][z] = max(f[i][j][z],f[i-1][j][z-a]+j);
,其中a是当前物品的体积。因为是完全背包,所以for循环从前往后进行。最后输出具体方案时,根据判断t>=j&&t2>=(int)pow(j,p)&&t-j==f[i][j][t2-(int)pow(j,p)]
是否成立来输出结果,其中t代表当前(当前是i+1,因为for循环从k-1开始)的总价值,t2代表当前的总体积,当当前(i+1)总价值减去当前(i+1)物品的价值等于重量为i,前j种物品,当前(i+1)总体积减去当前(i+1)物品体积的最大价值,说明该物品是被选中的,输出该物品即可。不相等说明该物品没被选中,继续遍历。另外还要保证t>=j&&t2>=(int)pow(j,p)
即剩余的体积与价值要比当前物品的大。
代码:
#include<iostream>
#include<cstring>
#include<string>
#include<algorithm>
#include<vector>
#include<map>
#include<queue>
#include<cstdio>
#include<cmath>
using namespace std;
int n,m,k,p,f[410][30][410];
int main(){
// freopen("1.txt","r",stdin);
cin>>n>>k>>p;
m=pow(n+1-k,(float)1/p);//直接设成20也行
memset(f,-0x3f,sizeof f);
f[0][0][0]=0;
for(int j=1;j<=m;j++){//遍历每种物品
f[0][j][0]=0;
for(int i=1;i<=k;i++){//遍历重量
int a=pow(j,p);
for(int z=1;z<=n;z++){//遍历体积
f[i][j][z]=f[i][j-1][z];
if(z>=a) f[i][j][z] = max(f[i][j][z],f[i-1][j][z-a]+j);
}
}
}
if(f[k][m][n]>0){
cout<<n<<" = ";
int t = f[k][m][n],t2=n,flag=0;
for(int i=k-1;i>=0;i--){
for(int j=m;j>=0;j--){
if(t>=j&&t2>=(int)pow(j,p)&&t-j==f[i][j][t2-(int)pow(j,p)]){
if(flag==0){
flag=1;
}else{
cout<<" + ";
}
cout<<j<<"^"<<p;
t-=j;
t2-=(int)pow(j,p);
break;
}
}
}
cout<<endl;
}else{
cout<<"Impossible"<<endl;
}
return 0;
}
雪靴(完全背包问题)(题目链接)
题目描述:
到冬天了,这意味着下雪了!
从农舍到牛棚的路上有 N 块地砖,方便起见编号为 1…N,第 i 块地砖上积了 fi 英尺的雪。
Farmer John 从 1 号地砖出发,他必须到达 N 号地砖才能叫醒奶牛们。
1 号地砖在农舍的屋檐下,N 号地砖在牛棚的屋檐下,所以这两块地砖都没有积雪。
但是在其他的地砖上,Farmer John 只能穿靴子了!
在 Farmer John 的恶劣天气应急背包中,总共有 B 双靴子,编号为 1…B。
其中某些比另一些结实,某些比另一些轻便。
具体地说,第 i 双靴子能够让 FJ 在至多 si 英尺深的积雪中行走,能够让 FJ 每步至多前进 di。
不幸的是,这些靴子都套在一起,使得 Farmer John 在任何时刻只能拿到最上面的那一双。
所以在任何时刻,Farmer John可以穿上最上面的一双靴子(抛弃原来穿着的那双),或是丢弃最上面的那一双靴子(使得可以拿到下面那一双)。
Farmer John 只有在地砖上的时候才能换靴子。
如果这块地砖的积雪有 f 英尺,他换下来的靴子和他换上的那双靴子都要能够承受至少 f 英尺的积雪。
中间没有穿就丢弃的靴子无需满足这一限制。
帮助 Farmer John 最小化他的消耗,确定他最少需要丢弃的靴子的双数。
你可以假设 Farmer John 在开始的时候没有穿靴子。
解题思路:
普通的完全背包问题,关键在于需要判断当前砖与被状态转移的砖的积雪的厚度是否不大于当前雪靴的承受能力。
代码:
#include<iostream>
#include<cstring>
#include<string>
#include<algorithm>
#include<vector>
#include<map>
#include<queue>
#include<cstdio>
#include<cmath>
using namespace std;
const int N = 1000010;
int n,b,f[N],s[N],d[N],dp[N];
int main(){
// freopen("1.txt","r",stdin);
cin>>n>>b;
dp[0]=1;
int nows;
for(int i=0;i<n;i++) cin>>f[i];
for(int i=0;i<b;i++){//遍历每双雪靴
cin>>s[i]>>d[i];
for(int j=1;j<n;j++){//遍历每个地砖
if(s[i]<f[j]) continue;
for(int k=1;k<=d[i]&&j>=k;k++){//遍历步数
if(dp[j-k]==1&&s[i]>=f[j-k]) dp[j]=1;
}
}
if(dp[n-1]==1){
cout<<i<<endl;
break;
}
}
return 0;
}