多重背包也是一种基本的背包问题模型,其基本特点是:每种物品有一个固定的装入次数上限。
多重背包问题的一般描述为:有N个物品,第i个物品的重量与价值分别为W[i]与P[i]且第i种物品最多有C[i] 件。背包容量为V,试问在每个物品不超过其上限的件数(物品必须保持完整)的情况下,如何让背包装入的物品具有更大的价值总和。
其一般解题思路为:
设f[i][j] 表示从编号1~i的物品中挑选任意数量的任意物品放入容量为j的背包中得到的最大价值,那么有 f[i][j]=max { f[i−1][j−k∗w[i]]+k∗v[i] ∣0≤k≤p[i]&k∗w[i]≤j }。
编写代码时,可以写成如下的循环。
for ( i = 1; i <= N; i++)
for ( j = 1; j <= V; j++)
for (k = 0; k <= C[i] && k * W[i] <= j; k++)
{
f[i][j] = max( f[i][j], f[i - 1][j - k * W[i]] + k *P[i]);
}
求得的最优值为f[N][V]。
同样多重背包也可以进行空间优化,将二维数组优化为一维数组,即
f[j]=max { f[j],f[j−k∗W[i]]+k∗P[i] } (0≤k && k∗W[i]≤j)
编写代码时,一般采用如下的循环。
for (i=1; i<=N; i++)
for (j=V; j>=0; j--)
for (k=0; k<=c[i] && k*W[i] <=j; k++)
f[j] = max( f[j], f[j - k * W[i]] + k *P[i]);
求得的最优值为f[V]。
从上面的程序代码可以看出,多重背包的处理一般采用三重循环,时间复杂度较高。为了对时间进行优化,可以采用二进制优化法将多重背包转变为0/1背包(采用二重循环)进行处理。
所谓二进制优化法就是将第 i 种c[i]件物品按二进制的方法分拆成s份“不同”的物品。例如,设第i件物品有20件,每件重量和价值分别为w和p,则可以分拆别5份“物品”如下:
第1份含有1件物品,重量为w,价值为p;
第2份含有2件物品,重量为2w,价值为2p;
第3份含有4件物品,重量为4w,价值为4p;
第4份含有8件物品,重量为8w,价值为8p;
第5份含有5件物品,重量为5w,价值为5p。
因为,1+2+4+8+5=20,则由这5份“物品”可组合成0~20件之间任意件数的物品。这5份“物品”在进行组合时,每份物品要么选取,要么不选取,正好就是对这5份物品进行0/1背包处理。由此,多重背包可以通过这种方式转化为0/1背包进行处理。从而将三重循环优化为二重循环处理。
我们知道,k位二进制数可以表示0~2k-1之间的任意整数,k位二进制数从低位到高位,各位上的权值依次为1(20)、2(21)、4(22)、…、2k-1。因此,对于任意一个正整数x,则可以将其拆分为1+2+4+…+2k-1+y(其中y是二进制拆分剩余的部分,y<2k)。
通过拆分后,就可以将多重背包问题转换为0/1背包问题求解了。
编写代码如下:
for (i=1; i<=N; i++)
{
int s =C[i];
for (j=1; j<=s; s-=j, j=j*2) // 二进制拆分
{
for (k =V; k >=0 && k>= j*W[i]; k--) // 0/1背包
{
f[k] = max(f[k], f[k - j*W[i]] + j *P[i]);
}
}
if (s > 0) // 拆分的剩余部分
{
for ( j =V; j >= s * W[i]; j--)
{
f[j] = max(f[j], f[j - s * W[i]] + s * P[i]);
}
}
}
问题描述
你准备自己采购一些粮食支援灾区,现在假设你一共有资金n元,而市场有m种大米,每种大米都是袋装产品,其重量和价格不等,并且只能整袋购买。
请问:你用有限的资金最多能采购多少公斤粮食呢?
输入
输入数据首先包含一个正整数C,表示有C组测试用例,每组测试用例的第一行是两个整数n和m(1<=n<=100,1<=m<=100),分别表示经费的金额和大米的种类,然后是m行数据,每行包含3个数p,h和c(1<=p<=20,1<=h<=200,1<=c<=20),分别表示每袋的价格、每袋的重量以及对应种类大米的袋数。
输出
对于每组测试数据,请输出能够购买大米的最多重量,你可以假设经费买不光所有的大米,并且经费你可以不用完。每个实例的输出占一行。
输入样例
1
8 2
2 100 4
4 100 2
输出样例
400
(1)编程思路。
典型的多重背包问题。按上面介绍的方法,采用二维数组编写源程序1;采用一维数组编写源程序2;采用二进制优化的方法编写源程序3。
(2)采用二维数组编写的源程序1。
#include <stdio.h>
#include <string.h>
int max(int a,int b)
{
return a>b?a:b;
}
int main()
{
int p[105],h[105],c[105],f[105][105];
int t;
scanf("%d",&t);
while (t--)
{
int n,m;
scanf("%d%d",&n,&m);
int i,j,k;
for (i=1;i<=m;i++)
scanf("%d%d%d",&p[i],&h[i],&c[i]);
memset(f,0,sizeof(f));
for (i=1;i<=m;i++)
for (j=1; j<=n; j++)
for (k=0; k<=c[i] && k*p[i] <=j; k++)
f[i][j]=max(f[i][j],f[i-1][j-k*p[i]]+k*h[i]);
printf("%d\n",f[m][n]);
}
return 0;
}
(3)采用一维数组编写的源程序2。
#include <stdio.h>
#include <string.h>
int max(int a,int b)
{
return a>b?a:b;
}
int main()
{
int p[105],h[105],c[105],f[105];
int t;
scanf("%d",&t);
while (t--)
{
int n,m;
scanf("%d%d",&n,&m);
int i,j,k;
for (i=1;i<=m;i++)
scanf("%d%d%d",&p[i],&h[i],&c[i]);
memset(f,0,sizeof(f));
for (i=1;i<=m;i++)
for (j=n;j>=0;j--)
for (k=0; k<=c[i] && k*p[i] <=j; k++)
f[j]=max(f[j],f[j-k*p[i]]+k*h[i]);
printf("%d\n",f[n]);
}
return 0;
}
(4)采用二进制优化的方法编写的源程序3。
#include <stdio.h>
#include <string.h>
int max(int a,int b)
{
return a>b?a:b;
}
int main()
{
int p[105],h[105],c[105],f[105];
int t;
scanf("%d",&t);
while (t--)
{
int n,m;
scanf("%d%d",&n,&m);
int i,j,k;
for (i=1;i<=m;i++)
scanf("%d%d%d",&p[i],&h[i],&c[i]);
memset(f,0,sizeof(f));
for (i=1; i<=m; i++)
{
int s =c[i];
for (j=1; j<=s; s-=j, j=j*2) // 二进制拆分
{
for (k =n; k >=0 && k>= j*p[i]; k--) // 0/1背包
{
f[k] = max(f[k], f[k - j*p[i]] + j *h[i]);
}
}
if (s > 0) // 拆分的剩余部分
{
for ( j =n; j >= s * p[i]; j--)
{
f[j] = max(f[j], f[j - s * p[i]] + s * h[i]);
}
}
}
printf("%d\n",f[n]);
}
return 0;
}
问题描述
小明的花店新开张,为了吸引顾客,他想在花店的门口摆上一排花,共m盆。通过调查顾客的喜好,小明列出了顾客最喜欢的n种花,从1到n标号。为了在门口展出更多种花,规定第i种花不能超过 ai盆,摆花时同一种花放在一起,且不同种类的花需按标号的从小到大的顺序依次摆列。
试编程计算,一共有多少种不同的摆花方案。
输入
第一行包含两个正整数n和m(0<n≤100,0<m≤100),中间用一个空格隔开。
第二行有n个整数,每两个整数之间用一个空格隔开,依次表示 a1,a2,…,an。
输出
一个整数,表示有多少种方案。注意:因为方案数可能很多,请输出方案数对 10^6+7取模的结果。
输入样例
2 4
3 2
输出样例
2
(1)编程思路。
典型的多重背包问题。按前面的介绍进行处理即可。
采用二维数组,定义int f[105][105]={0}。设f[i][j]表示前i种花中摆放j盆花的方案数,初始值初f[0][0]=1(什么也不摆)外,其余全部为0。可以编写如下的源程序1。
采用一维数组,int f[105]={0};设f[i]表示摆放i盆花的方案数。可以编写如下的源程序2。
(2)使用二维数组编写的源程序1。
#include <stdio.h>
int main()
{
int f[105][105]={0},a[105];
int n,m;
scanf("%d%d",&n,&m);
int i,j,k;
for (i=1; i<=n; i++)
{
scanf("%d",&a[i]);
}
f[0][0]=1;
for (i=1; i<=n; i++)
for (j=0; j<=m; j++)
for (k=0; k<=(a[i]<j?a[i]:j); k++)
f[i][j] = (f[i][j] + f[i-1][j-k])%1000007;
printf("%d\n",f[n][m]);
return 0;
}
(3)使用一维数组编写的源程序2。
#include <stdio.h>
int main()
{
int f[105]={0},a[105];
int n,m;
scanf("%d%d",&n,&m);
int i,j,k;
for (i=1; i<=n; i++)
scanf("%d",&a[i]);
f[0] = 1;
for (i=1; i<=n; i++)
for (j=m; j>=0; j--)
for (k=1; k<=(a[i]<j?a[i]:j); k++)
f[j] = (f[j] + f[j-k])%1000007;
printf("%d\n",f[m]);
return 0;
}
将上面的源程序1和2提交给洛谷题库P1077 [NOIP2012 普及组] 摆花(https://www.luogu.com.cn/problem/P1077),测评结果均为“Accepted”。
问题描述
有k棵樱花树,在第i棵树下最多能收集到 si朵樱花(收集了0朵樱花也算收集了樱花)。
你有多少种方案能够收集到恰好n朵樱花呢?
输入
第一行两个正整数 n,k,表示要收集n朵樱花,有k棵樱花树。
接下来一行k个正整数 s1,s2,…,sk,其中 si表示最多在第i棵樱花树下收集到si朵樱花。
输出
一行一个整数,表示恰好收集到n朵樱花的方案数。
由于答案可能太大,请输出答案对 10086001 取模后的值。
特殊地,如果收集不到n朵樱花,请输出一个字符串 impossible。
输入样例#1
3 4
1 1 1 1
输出样例 #1
5
输入样例 #2
10 9
9 6 8 7 9 6 5 4 3
输出样例#2
68345
输入样例 #3
10 5
2 2 2 2 1
输出样例 #3
Impossible
(1)编程思路1。
定义二维数组int f[5001][5001];其中f[i][j]表示前i颗樱花树下共收集到j朵樱花的方案数。显然,有f[i][0]=1(1≤i≤n,每颗树下可不收集樱花,收集了0朵樱花也算收集了樱花,方案数为1),还有f[1][j]=1(1≤j≤s[1],第1颗樱花树下可分别收集1~s[1]朵樱花)。
转移方程为: f[i][j]=f[i][j]+f[i-1][j-k] (其中k值为第i可樱花树收集樱花的朵数)。
(2)源程序1。
#include <stdio.h>
#include <string.h>
int f[5001][5001]={0};
int main()
{
int v,n;
scanf("%d%d",&v,&n);
int s[5001];
int i,j,k;
int tot=0;
for (i=1;i<=n;i++)
{
scanf("%d",&s[i]);
tot+=s[i];
}
if (tot<v)
{
printf("impossible\n");
return 0;
}
for (i=1;i<=n;i++)
f[i][0]=1;
for (i=1;i<=s[1];i++)
f[1][i]=1;
for (i=2; i<=n; i++)
{
for (j=1; j<=v; j++)
{
for (k=0; k<=s[i] && k<=j; k++)
{
f[i][j]=(f[i][j]+f[i-1][j-k])%10086001;
}
}
}
int ans=0;
for (i=1; i<=n; i++)
ans = (ans+f[i][v])%10086001;
printf("%d\n",ans);
return 0;
}
将上面的源程序1提交给洛谷题库P6394 樱花,还有你(https://www.luogu.com.cn/problem/P6394),测评结果为“Unaccepted”,其中测试点#17、测试点#19和测试点#20,显示“TLE”,测试点#18显示“MLE”。
(3)编程思路2。
由于数据量较大,因此采用二维数组存储,会存在超内存限制的情况,因此采用一维数组进行处理。
(4)源程序2。
#include <stdio.h>
#include <string.h>
int f[5001]={0};
int main()
{
int v,n;
scanf("%d%d",&v,&n);
int s[5001];
int i,j,k;
int tot=0;
for (i=1;i<=n;i++)
{
scanf("%d",&s[i]);
tot+=s[i];
}
if (tot<v)
{
printf("impossible\n");
return 0;
}
f[0]=1;
int ans=0;
for (i=1;i<=n;i++) // 前i棵树
{
for (j=v;j>=0;j--)
for (k=1;k<=s[i] && k<=j;k++)
f[j]=(f[j]+f[j-k])%10086001;
ans=(ans+f[v])%10086001;
}
printf("%d\n",ans);
return 0;
}
将上面的源程序2提交给洛谷题库P6394 樱花,还有你(https://www.luogu.com.cn/problem/P6394),测评结果仍为“Unaccepted”,其中测试点#17、测试点#18、测试点#19和测试点#20,均显示“TLE”。
(5)编程思路3。
源程序2中的多重背包采用三重循环实现,由于数据量较大,循环处理太耗时,因此会超时。观察第三层循环,每次的f[j]都是加上一个区间,所以可以直接用一个前缀和来计算,这样第三层循环就可以省掉了。
(6)源程序3。
#include <stdio.h>
int main()
{
int v,n;
scanf("%d%d",&v,&n);
int f[5001]={0};
int s[5001];
int sum[5001]={0}; // 保存前缀和
int i,j;
int tot=0;
for (i=1;i<=n;i++)
{
scanf("%d",&s[i]);
tot+=s[i];
}
if (tot<v)
{
printf("impossible\n");
return 0;
}
sum[0]=f[0]=1;
int ans=0;
for (i=1;i<=n;i++) // 前i棵树
{
for (j=1;j<=v;j++) // 更新前缀和
sum[j]=(sum[j-1]+f[j])% 10086001;
for (j=v;j>=1;j--)
{
int k=s[i]<j?s[i]:j;
if (k==j) f[j]=(f[j]+sum[j-1])% 10086001;
else f[j]=(f[j]+sum[j-1]-sum[j-k-1]+10086001)% 10086001; //利用前缀和
}
ans=(ans+f[v])%10086001;
}
printf("%d\n",ans);
return 0;
}
将上面的源程序3提交给洛谷题库P6394 樱花,还有你(https://www.luogu.com.cn/problem/P6394),测评结果为“Accepted”。
问题描述
设有1g、2g、3g、5g、10g、20g的砝码各若干枚(其总重≤1000),计算用这些砝码能称出的不同重量的个数N,但不包括一个砝码也不用的情况。
输入
输入方式:a1 , a2 ,a3 , a4 , a5 ,a6(表示1g砝码有a1个,2g砝码有a2个,…,20g砝码有a6个)。
输出
输出方式:Total=N
输入样例
1 1 0 0 0 0
输出样例
Total=3
(1)编程思路。
设f[i]表示重量i是否可以称出。初始时,f[0]=1,其余全部为0。输入6种砝码的个数w[i],计算出所有砝码全部使用时,可称出的总重量tot,这也是背包的容量。
将6种砝码按多重背包的处理方法加入背包,若重量j-w[i]*k可称出,即f[j-w[i]*k]==1,则加上k个重w[i]的砝码后,重量j也可以称出,置f[j]=1。
多重背包处理完后,f[1]~f[tot]元素值为1的个数就是可称出的不同重量的个数。
(2)源程序。
#include <stdio.h>
int main()
{
int f[1001]={0};
int w[7]={0,1,2,3,5,10,20};
int a[7];
int i,j,k;
int tot=0; // 总重量
for (i=1;i<=6;i++)
{
scanf("%d",&a[i]);
tot+=w[i]*a[i];
}
f[0]=1;
for (i=1;i<=6;i++)
{
for (j=tot;j>=0;j--)
for (k=0;k<=a[i];k++)
{
if (j-w[i]*k>=0 && f[j-w[i]*k]!=0)
f[j]=1;
}
}
int ans=0;
for (i=1;i<=tot;i++)
if (f[i]!=0) ans++;
printf("Total=%d\n",ans);
return 0;
}
将上面的源程序提交给洛谷题库P2347 [NOIP1996 提高组] 砝码称重(https://www.luogu.com.cn/problem/P2347),测评结果为“Accepted”。
问题描述
Jimmy 到 Symbol 的手表店买手表,Jimmy 只带了n种钱币,第i种钱币的面额为 ki元,张数为 ai张。Symbol 的店里一共有m 块手表,第i 块手表的价格为 ti元。
Symbol 的手表店不能找零,所以 Jimmy 只能在凑出恰好的钱数时才能购买一块手表。现在对于店里的每块手表,Jimmy 想知道他能不能凑出恰好的钱数进行购买。
输入
第一行两个空格分隔的整数 n(1≤n≤200)和 m(1≤m≤100000) 表示钱币数与手表数。
接下来 n 行每行两个空格分隔的整数 ki(1≤ki≤500000)和 ai(1≤ai≤1000)表示钱币的面额和张数。
第 n+2行,共 m个用空格分隔的整数 ti(0≤ti≤500000),表示每块手表的价格。
输出
一共m 行,对于第i 行,如果能凑出恰好的钱数购买第i 块手表则输出 Yes 否则输出 No,注意只有首字母大写。
输入样例
3 5
1 2
5 1
6 3
3 19 21 1 7
输出样例
No
Yes
No
Yes
Yes
(1)编程思路。
设f[i]表示钱数i是否可以用n种钱币凑出。初始时,f[0]=1,其余全部为0。由于每种钱币的张数较多(最多可达1000张),因此按照前面介绍的二进制数优化方法,将多重背包变化成0/1背包进行处理。
(2)源程序。
#include <stdio.h>
#include <string.h>
int main()
{
int f[500005]={0},w[2005];
int n,m;
scanf("%d%d",&n,&m);
int i,j;
int cnt=0;
for (i=1; i<=n; i++)
{
int k,a;
scanf("%d%d",&k,&a);
for (j=1; j<=a; j*=2) // 多重背包转成0/1背包
{
w[++cnt]=j*k;
a-=j;
}
if (a>0)
{
w[++cnt]=k*a;
}
}
f[0]=1;
for (i=1; i<=cnt; i++) // 01背包的求解
for(j=500000; j>=w[i]; j--)
if (f[j-w[i]]) f[j]=1;
for (i=1;i<=m;i++)
{
int t;
scanf("%d",&t);
if (f[t]) printf("Yes\n");
else printf("No\n");
}
return 0;
}
将上面的源程序提交给洛谷题库P6567 [NOI Online #3 入门组] 买表(https://www.luogu.com.cn/problem/P1776),测评结果为“Accepted”。
问题描述
正在上大学的小皮球热爱英雄联盟这款游戏,而且打的很菜,被网友们戏称为「小学生」。
现在,小皮球终于受不了网友们的嘲讽,决定变强了,他变强的方法就是:买皮肤!
小皮球只会玩N个(5≤N≤150)英雄,因此,他也只准备给这N个英雄买皮肤,并且决定,以后只玩有皮肤的英雄。
这N个英雄中,第i个英雄有Ki 款皮肤,价格是每款 Ci个Q 币(同一个英雄的皮肤价格相同)。
为了让自己看起来高大上一些,小皮球决定给同学们展示一下自己的皮肤,展示的思路是这样的:对于有皮肤的每一个英雄,随便选一个皮肤给同学看。
比如,小皮球共有 5 个英雄,这 5 个英雄分别有0,0,3,2,4 款皮肤,那么,小皮球就有3×2×4=24 种展示的策略。
现在,小皮球希望自己的展示策略能够至少达到M (M≤1017)种,请问,小皮球至少要花多少钱呢?
输入
第一行,两个整数N,M。
第二行,N个整数,表示每个英雄的皮肤数量Ki。
第三行,N 个整数,表示每个英雄皮肤的价格Ci 。
输出
一个整数,表示小皮球达到目标最少的花费。
输入样例
3 24
4 4 4
2 2 2
输出样例
18
(1)编程思路。
展示方案达到M种作为背包的容量,每一个英雄都有一个皮肤数量、一个购买的Q币数(花费),将每个英雄的皮肤看成物品,就是有限物品数量的多重背包问题。
设f[j]表示花费j个Q币购买英雄皮肤可得到的最大展示方案数量,则状态转移方程为:
f[j]=max(f[j−p∗c[i]]∗p,f[j]),其中 p是当前英雄所选的皮肤数量,c[i]该皮肤的购买价格。
多重背包处理完后,用变量i从0~tot搜索f[i]的判断,第1次遇到的 f[i]>=m的i值就是所求的最小花费。
(2)源程序。
#include <stdio.h>
long long max(long long a,long long b)
{
return a>b?a:b;
}
long long f[250000]={0};
int main()
{
int n;
long long m;
scanf("%d%lld",&n,&m);
int i,j,p;
int k[155],c[155];
for (i=1;i<=n;i++)
scanf("%d",&k[i]);
int tot=0; // 花的总钱数
for (i=1;i<=n;i++)
{
scanf("%d",&c[i]);
tot+=c[i]*k[i];
}
f[0]=1;
for (i=1;i<=n;i++)
{
for (j=tot;j>=0;j--)
for (p=0;p<=k[i];p++)
{
if (j-c[i]*p>=0)
f[j]=max(f[j],1L*f[j-c[i]*p]*p);
}
}
for (i=0;i<=tot;i++)
if (f[i]>=m)
{
printf("%d\n",i);
break;
}
return 0;
}
将上面的源程序提交给洛谷题库P5365 [SNOI2017]英雄联盟(https://www.luogu.com.cn/problem/P5365),测评结果为“Accepted”。
问题描述
小F找到了王室的宝物库,里面堆满了无数价值连城的宝物。但是这里的宝物实在是太多了,小F的采集车似乎装不下那么多宝物。看来小F只能含泪舍弃其中的一部分宝物了。
小F对库里的宝物进行了整理,他发现每样宝物都有一件或者多件。他粗略估算了下每样宝物的价值,之后开始了宝物筛选工作:小F有一个最大载重为W的采集车,宝物库里总共有n种宝物,每种宝物的价值为vi,重量为wi,每种宝物有mi件。小F希望在采集车不超载的前提下,选择一些宝物装进采集车,使得它们的价值和最大。
输入
第一行为一个整数n(1≤n≤100)和W(0≤W≤40000),分别表示宝物种数和采集车的最大载重。
接下来n行每行三个整数vi,wi,mi。n≤∑mi ≤100000。
输出
输出仅一个整数,表示在采集车不超载的情况下收集的宝物的最大价值。
输入样例
4 20
3 9 3
5 9 1
9 4 2
8 1 3
输出样例
47
(1)编程思路1。
典型的多重背包问题,按前面介绍的采用一维数组用三重循环编写源程序1。
(2)采用一维数组用三重循环编写的源程序1。
#include <stdio.h>
#include <string.h>
int max(int a,int b)
{
return a>b?a:b;
}
int main()
{
int n,limw;
scanf("%d%d",&n,&limw);
int i,j,k;
int f[40005];
memset(f,0,sizeof(f));
for (i=1;i<=n;i++)
{
int v,w,m;
scanf("%d%d%d",&v,&w,&m);
for (j=limw;j>=0;j--)
for (k=0; k<=m && k*w<=j; k++)
f[j]=max(f[j],f[j-k*w]+k*v);
}
printf("%d\n",f[limw]);
return 0;
}
将上面的源程序提交给洛谷题库P1776 宝物筛选(https://www.luogu.com.cn/problem/P1776),测评结果为“Unaccepted”,其中测试点#4~测试点#10,均显示“TLE”。
(3)编程思路2。
采用一维数组用三重循环编写的源程序1显示超时了,为了进行时间优化,采用二进制拆分优化法将多重背包转换为0/1背包进行处理,编写下面的源程序2。
(4)采用二进制拆分优化法编写的源程序2。
#include <stdio.h>
#include <string.h>
int main()
{
int n,limw;
scanf("%d%d",&n,&limw);
int v[100005],w[100005];
int i,j;
int cnt=0;
for (i=1;i<=n;i++)
{
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
for (j=1;j<=c;j<<=1)
{
v[++cnt]=j*a;
w[cnt]=j*b;
c-=j;
}
if (c)
{
v[++cnt]=a*c;
w[cnt]=b*c;
}
}
int f[40005];
memset(f,0,sizeof(f));
for (i=1;i<=cnt;i++) // 转换为cnt种宝物
for (j=limw;j>=w[i];j--)
if (f[j]<f[j-w[i]]+v[i]) f[j]=f[j-w[i]]+v[i];
printf("%d\n",f[limw]);
return 0;
}
将上面的源程序提交给洛谷题库P1776 宝物筛选(https://www.luogu.com.cn/problem/P1776),测评结果为“Accepted”。
问题描述
Life种了一块田,里面种了有一些桃树。
Life对PFT说:“我给你一定的时间去摘桃,你必须在规定的时间之内回到我面前,否则你摘的桃都要归我吃!”
PFT思考了一会,最终答应了!
由于PFT的数学不好!它并不知道怎样才能在规定的时间获得最大的价值,
由于PFT不是机器人,所以他的体力并不是无限的,他不想摘很多的桃以至体力为0,而白白把桃给Life。同时PFT每次只能摘一棵桃树,每棵桃树都可以摘K次(对于同一棵桃每次摘的桃数相同)。每次摘完后都要返回出发点(PFT一次拿不了很多)即Life的所在地(0,0),设试验田左上角的桃树坐标是(1,1)。
PFT每秒只能移动一个单位,每移动一个单位耗费体力1(摘取不花费时间和体力,但只限上下左右移动)。
输入
第一行:四个数为N,M,TI,A(10≤N,M,TI,A≤100)分别表示试验田的长和宽,Life给PFT的时间,和PFT的体力。
下面一个N行M列的矩阵桃田。表示每次每棵桃树上能摘的桃数。
接下来N行M列的矩阵,表示每棵桃最多可以采摘的次数K。
输出
一个数:PFT可以获得的最大的桃个数。
输入样例
4 4 13 20
10 0 0 0
0 0 10 0
0 0 10 0
0 0 0 0
1 0 0 0
0 0 2 0
0 0 4 0
0 0 0 0
输出样例
10
(1)编程思路。
定义数组int dist[10005],num1[10005],num2[10005]; ,分别用于保存每颗桃树到(0,0)的距离、树上的桃子数、可采摘的次数。
在输入后进行预处理,每块桃树田中把能摘到桃子的(桃子数量>0)并且可采摘次数>0桃树相关信息分别保存到dist、num1和num2这三个数组中。
这样,数组中的每颗桃树可以看成一件物品,问题转变为给定体力V、桃树数量N(能摘到桃子的)、每颗桃树最多被采摘K次,在这种情况下最多能摘多少桃子。就是一个典型的多重背包问题了。
背包容量为V,V受两个条件约束,Life给PFT的时间TI和PFT的体力A。Life给PFT的时间可以转换为体力,因为PFT每秒只能移动一个单位,每移动一个单位耗费体力1,因此给定时间TI就是可供PFT消耗的体力,另外PFT的体力是他实际拥有的体力,FPT最后回去时,体力不能为0,因此至少得留一格体力,这样可确定背包容量V取TI和A-1中的较小值。
(2)源程序。
#include <stdio.h>
long long max(long long a,long long b)
{
return a>b?a:b;
}
int main()
{
int n,m,x,y,v;
scanf("%d%d%d%d",&n,&m,&x,&y);
v=x<(y-1)?x:y-1;
int i,j,k;
int map[105][105];
long long f[105]={0};
for (i=1;i<=n;i++)
for (j=1;j<=m;j++)
scanf("%d",&map[i][j]);
int dist[10005],num1[10005],num2[10005]; // 分别表示桃树的距离、桃子数、可采摘的次数
int cnt=0;
for (i=1;i<=n;i++)
for (j=1;j<=m;j++)
{
int a;
scanf("%d",&a);
if (a>0 && map[i][j]>0)
{
dist[cnt]=2*(i+j);
num1[cnt]=map[i][j];
num2[cnt]=a;
cnt++;
}
}
for (i=0;i<cnt;i++)
for (j=v;j>=0;j--)
for (k=1;k<=num2[i] && k*dist[i]<=j;k++)
f[j]=max(f[j],f[j-k*dist[i]]+k*num1[i]);
printf("%lld\n",f[v]);
return 0;
}
将上面的源程序提交给洛谷题库P2760 科技庄园(https://www.luogu.com.cn/problem/P2760),测评结果为“Accepted”。
问题描述
有面值分别为A1、A2、…、An的n种硬币,每种硬币各有C1、C2、…、Cn枚。用这些硬币可以组合成多少种不同的不超过m的钱数。
输入
输入包含几个测试用例。每个测试用例的第一行包含两个整数n(1<=n<=100),m(m<=100000)。第二行包含2n个整数,表示A1,A2,…,An,C1,C2,…,Cn(1<=Ai<=100000,1<=Ci<=1000)。最后一个测试用例后跟两个零。
输出
对于每个测试用例,在一行上输出答案。
输入样例
3 10
1 2 4 2 1 1
2 5
1 4 2 1
0 0
输出样例
8
4
(1)编程思路1。
n种硬币,设第i种硬币的面值为Ai,数量为Ci,将这些硬币装入容量为m的背包中,求这些硬币能够组成从1到m中的哪些数字。
多重背包问题,可采用二进制拆分优化的方法编写如下的源程序1.
(2)源程序1。
#include <stdio.h>
#include <string.h>
int main()
{
int a[101], c[101],w[101];
int f[100005];
int n, m;
while (scanf("%d%d",&n,&m) && (n||m))
{
int i,j;
for (i=1; i<=n; i++)
scanf("%d",&a[i]);
for (i=1; i<=n; i++)
scanf("%d",&c[i]);
memset(f,0,sizeof f);
f[0] = 1;
int ans = 0;
for (i=1; i<=n; i++)
{
int k,cnt=0;
for (j=1; j<=c[i]; j*=2) // 多重背包转成0/1背包
{
w[++cnt]=j*a[i];
c[i]-=j;
}
if (c[i]>0)
{
w[++cnt]=c[i]*a[i];
}
for (j=1; j<=cnt; j++)
for(k=m; k>=w[j]; k--)
if (!f[k] && f[k -w[j]])
{
f[k] = 1;
ans++;
}
}
printf("%d\n",ans);
}
return 0;
}
将上面的源程序1提交给北大OJ题库POJ 1742 Coins(http://poj.org/problem?id=1742),测评结果为“Time Limit Exceeded”,超时。
(3)编程思路2。
可以这样来考虑问题。
对于第i种硬币,如果A i ∗ C i≥m,相当于Ai取的个数没有限制,即可以把第i种硬币的数量视为无穷,作为一个完全背包来求解;否则,就作为一个多重背包来求解。
(4)源程序2。
#include <stdio.h>
#include <string.h>
int main()
{
int a[101], c[101],w[101];
int f[100005];
int n, m;
while (scanf("%d%d",&n,&m) && (n||m))
{
int i,j;
for (i=1; i<=n; i++)
scanf("%d",&a[i]);
for (i=1; i<=n; i++)
scanf("%d",&c[i]);
memset(f,0,sizeof f);
f[0] = 1;
int ans = 0;
for (i=1; i<=n; i++)
{
if (a[i] * c[i]>= m)
{
for (j=a[i]; j<=m; j++) // 完全背包
if (!f[j] && f[j - a[i]])
{
f[j] = 1;
ans++;
}
}
else
{
int k,cnt=0;
for (j=1; j<=c[i]; j*=2) // 多重背包转成0/1背包
{
w[++cnt]=j*a[i];
c[i]-=j;
}
if (c[i]>0)
{
w[++cnt]=c[i]*a[i];
}
for (j=1; j<=cnt; j++)
for(k=m; k>=w[j]; k--)
if (!f[k] && f[k -w[j]])
{
f[k] = 1;
ans++;
}
}
}
printf("%d\n",ans);
}
return 0;
}
将上面的源程序2提交给北大OJ题库POJ 1742 Coins(http://poj.org/problem?id=1742),测评结果为“Accepted”。
(5)编程思路3。
也可以这样考虑问题。
定义数组int f[100010],其中f[i]表示容量为i的背包是否可以装满,即钱数i是否可以构成。f[i]=1表示i可以构成。初始化时,将数组f全部设为0,f[ 0 ]设为1。
利用双重循环,外循环i从0到n-1遍历每种硬币A[ i ],内循环 j 从A[i]开始往后遍历到背包容量m,只要f[ j-A[i] ]==1(即表示钱数j-A[i]能够构成),且f[j]==0(表示钱数j 尚未被构成),则钱数 j 是有可能构成的。
为什么说有可能呢?是因为能否构成钱数 j,还得看A[i]的数量是否达到上限C[i]。
为了记录硬币A[i]的使用数量,定义一个专门记录个数的数组sum[M],在第一层循环内将数组sum[ ]初始化为0,一旦满足f[j -A[i] ] && ! f[ j ] && num[ j - A[ i ] ]<C[i] ,则说明钱数j是可以构成的,则将f[ j ]的值置为1,再将sum[ j ]=num[ j - A[i ] ]+1 表示构成钱数j所对应的面值为A[ i ]的硬币的使用数在钱数为 j-A[ i ]的基础上加1。有了这个sum数组,可以保证硬币A[i]的使用次数不会超过C[i]。
(6)源程序3。
#include <stdio.h>
#include <string.h>
int f[100010], sum[100010];
int main()
{
int n,m,i,j,cnt;
int a[101],c[101];
while (scanf("%d%d",&n,&m) && n!=0 && m!=0)
{
for (i=0;i<n;i++)
scanf("%d",&a[i]);
for (i=0;i<n;i++)
scanf("%d",&c[i]);
memset(f, 0, sizeof(f));
f[0] = 1;
cnt = 0;
for (i=0; i<n; i++)
{
memset(sum, 0, sizeof(sum));
for (j=a[i]; j<= m; j++)
{
if (!f[j] && f[j - a[i]] && sum[j - a[i]] < c[i])
{
f[j] = 1;
sum[j] = sum[j-a[i]] + 1;
cnt++;
}
}
}
printf("%d\n",cnt);
}
return 0;
}
将上面的源程序3提交给北大OJ题库POJ 1742 Coins(http://poj.org/problem?id=1742),测评结果为“Accepted”。
问题描述
某咖啡自动售货机只接收面值为1美分、5美分、10美分和25美分的硬币。给定你手里拥有的这4种硬币中每种硬币的数量以及咖啡价格。请给出支付咖啡时必须使用的硬币,要求你使用尽可能多的硬币个数,并且不能找零。
输入
输入包括多组测试用例,每个测试用例用一行输入。
每行输入包含五个整数,每个数用一个空格分隔,描述一种需要解决的情况。第1个整数为P(1<=P<=10000),是以美分为单位的咖啡价格。接下来的四个整数,C1,C2,C3,C4(0<=Ci<=10000),是1美分、5美分、10美分和25美分的硬币个数。输入的最后一行包含五个零,作为测试用例的结束。
输出
对于每种情况,都应该输出一行,其中包含字符串“Throw in T1 cents、T2 nickels、T3 dimes和T4 quarters”,其中T1、T2、T3、T4是在使用尽可能多的硬币的同时,应该使用适当价值的硬币来支付咖啡。如没有足够的零钱来支付咖啡的价格,程序应该输出“Charlie cannot buy coffee.”。
输入样例
12 5 3 1 2
16 0 0 0 1
0 0 0 0 0
输出样例
Throw in 2 cents, 2 nickels, 0 dimes, and 0 quarters.
Charlie cannot buy coffee.
(1)编程思路。
4种硬币,面值分别为V[1]、V[2]、V[3]、V[4],每种硬币的个数分别为C[1]、C[2]、C[3]、C[4]用这4种硬币来组成咖啡的支付价格P。典型的多重背包问题。
本题在使用多重背包求解时,有两个关键点。
1)在一般的背包问题里,有容量、价值,所要求的不是最大价值就是最小价值,而定义的f[i][j]表示装有i件物品,容量为j的最大值为f[i][j],简化为一维的f[j]表示的是在容量为j的时候最大价值为f[j]。在状态转移时,f[j]可以从f[j-v[i]]那边得到值,但它并不需要去判断在f[j-v[i]]前面是否有数可以组成f[j-v[i]],因此f[j-v[i]]为不为0,不影响最终结果。本题中,f[j]表示构成支付价格j时需要使用的最多硬币数。在状态转移的时候要保证f[j-v[i]]>0(初始时,f[0]=1,其余元素全为0),这是因为题意是要求组成P分钱需要的最多硬币数量,这样要可以组成f[j],f[j-v[i]]必须要可以组成,否则就会出错。同时,还要保证f[j-v[i]]+1>f[j](用了面值为V[i]的硬币后,使用的硬币数得多一些才行)且硬币的使用个数不超过c[i](可参考上面例9的编程思路3)。
2)要求出各种硬币使用多少个,需要记录路径,为此定义数组path[10010],在状态由f[j-v[i]]到f[j]转移时,记录path[j]=j-v[i],这样,j-path[j]=j-(j-v[i])=v[i],而v[i]正好是硬币的面值,可以对相应面值的硬币使用个数进行计数。
(2)源程序。
#include <stdio.h>
#include <string.h>
int main()
{
int f[10010],ans[10010],num[10010],path[10010];
int c[5]={0,1,5,10,25};
int t[5],p;
while (scanf("%d%d%d%d%d",&p,&t[1],&t[2],&t[3],&t[4])&&(p||t[1]||t[2]||t[3]||t[4]))
{
memset(f,0,sizeof(f));
memset(ans,0,sizeof(ans));
memset(path,0,sizeof(path));
f[0]=1;
int i,j;
for (i=1;i<=4;i++)
{
memset(num,0,sizeof(num));
for (j=c[i];j<=p;j++)
if (f[j-c[i]] && f[j-c[i]]+1>f[j] && num[j-c[i]]<t[i])
{
f[j]=f[j-c[i]]+1;
num[j]=num[j-c[i]]+1;
path[j]=j-c[i];
}
}
if (f[p]>0)
{
i=p;
while(i!=0)
{
ans[i-path[i]]++;
i=path[i];
}
printf("Throw in %d cents, %d nickels, %d dimes, and %d quarters.\n",ans[1],ans[5],ans[10],ans[25]);
}
else
printf("Charlie cannot buy coffee.\n");
}
return 0;
}
将上面的源程序提交给北大OJ题库POJ 1787 Charlie's Change(http://poj.org/problem?id=1787),可以Accepted。
1.P6771 [USACO05MAR]Space Elevator 太空电梯(https://www.luogu.com.cn/problem/P6771)
#include <stdio.h>
#include <string.h>
struct Node
{
int h,a,c;
};
int max(int a,int b)
{
return a>b?a:b;
}
int main()
{
int k;
scanf("%d",&k);
struct Node block[405];
int f[40500]={0};
int i,j;
for (i=1;i<=k;i++)
{
scanf("%d%d%d",&block[i].h,&block[i].a,&block[i].c);
}
for (i=1;i<k;i++)
for (j=1;j<=k-i;j++)
if (block[j].a>block[j+1].a)
{
struct Node temp;
temp=block[j]; block[j]=block[j+1]; block[j+1]=temp;
}
for (i=1;i<=k;i++)
{
int n;
for (n=1;n<=block[i].c;n++) //多重背包
{
for (j=block[i].a;j>=block[i].h;j--)
{
f[j]=max(f[j],f[j-block[i].h]+block[i].h);
}
}
}
int ans=0;
for (i=1;i<=block[k].a;i++)
ans=max(ans,f[i]);
printf("%d\n",ans);
return 0;
}
参考源程序1#include <stdio.h>
#include <string.h>
struct Node
{
int h,a,c;
};
int max(int a,int b)
{
return a>b?a:b;
}
int main()
{
int k;
scanf("%d",&k);
struct Node block[405];
int f[40500]={0};
int i,j;
for (i=1;i<=k;i++)
{
scanf("%d%d%d",&block[i].h,&block[i].a,&block[i].c);
}
for (i=1;i<k;i++)
for (j=1;j<=k-i;j++)
if (block[j].a>block[j+1].a)
{
struct Node temp;
temp=block[j]; block[j]=block[j+1]; block[j+1]=temp;
}
for (i=1;i<=k;i++)
{
if (block[i].a>block[i].c*block[i].h)
{
int n;
for (n=1;n<block[i].c;n*=2)
{
for (j=block[i].a;j>=block[i].h*n;j--)
{
f[j]=max(f[j],f[j-block[i].h*n]+block[i].h*n);
}
block[i].c-=n;
}
if (block[i].c>0)
{
for (j=block[i].a;j>=block[i].h*block[i].c;j--)
f[j]=max(f[j],f[j-block[i].h*block[i].c]+block[i].h*block[i].c);
}
}
else
{
for (j=block[i].h;j<=block[i].a;j++)
{
f[j]=max(f[j],f[j-block[i].h]+block[i].h);
}
}
}
int ans=0;
for (i=1;i<=block[k].a;i++)
ans=max(ans,f[i]);
printf("%d\n",ans);
return 0;
}
参考源程序22.POJ 1014 Dividing(http://poj.org/problem?id=1014)
#include <stdio.h>
#define INF 100000000
int f[240005];
int max(int a,int b)
{
return a>b?a:b;
}
int main()
{
int test=1,c[7],w[1001],n[7];
while (1)
{
int i,j;
for (i=1;i<=6;i++)
scanf("%d",&n[i]);
if (n[1]==0 && n[2]==0 && n[3]==0 && n[4]==0 && n[5]==0 && n[6]==0)
break;
int SumValue=0;
for (i=1;i<=6;i++)
{
c[i] = i;
SumValue+=i*n[i];
}
printf("Collection #%d:\n",test++);
if (SumValue%2) // 总价值为奇数,无法平分
{
printf("Can't be divided.\n\n");
}
else
{
int v = SumValue/2;
for (i = 1; i <=v;i++)
f[i]=-INF;
f[0] = 0;
for (i = 1; i <= 6; i++)
{
if (c[i] * n[i] >= v) //该种物品足以塞满背包-->转化为完全背包
{
for (j= c[i]; j<= v; j++)
f[j] = max(f[j], f[j-c[i]] + c[i]);
}
else
{
int k,cnt=0;
for (j=1; j<=n[i]; j*=2) // 多重背包转成0/1背包
{
w[++cnt]=j*c[i];
n[i]-=j;
}
if (n[i]>0)
{
w[++cnt]=c[i]*n[i];
}
for (j=1; j<=cnt; j++)
for (k=v; k>=w[j]; k--)
f[k] = max(f[k], f[k - w[j]] + w[j]);
}
}
if(f[v] < 0)
printf("Can't be divided.\n\n");
else
printf("Can be divided.\n\n");
}
}
return 0;
}
View Code3.POJ1276 Cash Machine(http://poj.org/problem?id=1276)
#include <stdio.h>
#include <string.h>
int max(int a,int b)
{
if (a>b) return a;
else return b;
}
int main()
{
int f[100005],w[1000];
int c,n;
while (scanf("%d%d",&c,&n)!=EOF)
{
int cnt=0;
memset(f,0,sizeof(f));
int i,j;
for (i=1; i<=n; i++)
{
int k,d;
scanf("%d%d",&k,&d);
for (j=1; j<=k; j*=2) // 多重背包转成0/1背包
{
w[++cnt]=j*d;
k-=j;
}
if (k>0)
{
w[++cnt]=k*d;
}
}
for (i=1; i<=cnt; i++) // 01背包的求解
for(j=c; j>=w[i]; j--)
f[j]=max(f[j],f[j-w[i]]+w[i]);
printf("%d\n",f[c]);
}
return 0;
}
View Code
我想为Heroku构建一个Rails3应用程序。他们使用Postgres作为他们的数据库,所以我通过MacPorts安装了postgres9.0。现在我需要一个postgresgem并且共识是出于性能原因你想要pggem。但是我对我得到的错误感到非常困惑当我尝试在rvm下通过geminstall安装pg时。我已经非常明确地指定了所有postgres目录的位置可以找到但仍然无法完成安装:$envARCHFLAGS='-archx86_64'geminstallpg--\--with-pg-config=/opt/local/var/db/postgresql90/defaultdb/po
尝试通过RVM将RubyGems升级到版本1.8.10并出现此错误:$rvmrubygemslatestRemovingoldRubygemsfiles...Installingrubygems-1.8.10forruby-1.9.2-p180...ERROR:Errorrunning'GEM_PATH="/Users/foo/.rvm/gems/ruby-1.9.2-p180:/Users/foo/.rvm/gems/ruby-1.9.2-p180@global:/Users/foo/.rvm/gems/ruby-1.9.2-p180:/Users/foo/.rvm/gems/rub
我的最终目标是安装当前版本的RubyonRails。我在OSXMountainLion上运行。到目前为止,这是我的过程:已安装的RVM$\curl-Lhttps://get.rvm.io|bash-sstable检查已知(我假设已批准)安装$rvmlistknown我看到当前的稳定版本可用[ruby-]2.0.0[-p247]输入命令安装$rvminstall2.0.0-p247注意:我也试过这些安装命令$rvminstallruby-2.0.0-p247$rvminstallruby=2.0.0-p247我很快就无处可去了。结果:$rvminstall2.0.0-p247Search
由于fast-stemmer的问题,我很难安装我想要的任何rubygem。我把我得到的错误放在下面。Buildingnativeextensions.Thiscouldtakeawhile...ERROR:Errorinstallingfast-stemmer:ERROR:Failedtobuildgemnativeextension./System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/bin/rubyextconf.rbcreatingMakefilemake"DESTDIR="cleanmake"DESTDIR=
当我尝试安装Ruby时遇到此错误。我试过查看this和this但无济于事➜~brewinstallrubyWarning:YouareusingOSX10.12.Wedonotprovidesupportforthispre-releaseversion.Youmayencounterbuildfailuresorotherbreakages.Pleasecreatepull-requestsinsteadoffilingissues.==>Installingdependenciesforruby:readline,libyaml,makedepend==>Installingrub
我正在尝试使用boilerpipe来自JRuby。我看过guide从JRuby调用Java,并成功地将它与另一个Java包一起使用,但无法弄清楚为什么同样的东西不能用于boilerpipe。我正在尝试基本上从JRuby中执行与此Java等效的操作:URLurl=newURL("http://www.example.com/some-location/index.html");Stringtext=ArticleExtractor.INSTANCE.getText(url);在JRuby中试过这个:require'java'url=java.net.URL.new("http://www
我意识到这可能是一个非常基本的问题,但我现在已经花了几天时间回过头来解决这个问题,但出于某种原因,Google就是没有帮助我。(我认为部分问题在于我是一个初学者,我不知道该问什么......)我也看过O'Reilly的RubyCookbook和RailsAPI,但我仍然停留在这个问题上.我找到了一些关于多态关系的信息,但它似乎不是我需要的(尽管如果我错了请告诉我)。我正在尝试调整MichaelHartl'stutorial创建一个包含用户、文章和评论的博客应用程序(不使用脚手架)。我希望评论既属于用户又属于文章。我的主要问题是:我不知道如何将当前文章的ID放入评论Controller。
首先回顾一下拉格朗日定理的内容:函数f(x)是在闭区间[a,b]上连续、开区间(a,b)上可导的函数,那么至少存在一个,使得:通过这个表达式我们可以知道,f(x)是函数的主体,a和b可以看作是主体函数f(x)中所取的两个值。那么可以有, 也就意味着我们可以用来替换 这种替换可以用在求某些多项式差的极限中。方法: 外层函数f(x)是一致的,并且h(x)和g(x)是等价无穷小。此时,利用拉格朗日定理,将原式替换为 ,再进行求解,往往会省去复合函数求极限的很多麻烦。使用要注意:1.要先找到主体函数f(x),即外层函数必须相同。2.f(x)找到后,复合部分是等价无穷小。3.要满足作差的形式。如果是加
SPI接收数据左移一位问题目录SPI接收数据左移一位问题一、问题描述二、问题分析三、探究原理四、经验总结最近在工作在学习调试SPI的过程中遇到一个问题——接收数据整体向左移了一位(1bit)。SPI数据收发是数据交换,因此接收数据时从第二个字节开始才是有效数据,也就是数据整体向右移一个字节(1byte)。请教前辈之后也没有得到解决,通过在网上查阅前人经验终于解决问题,所以写一个避坑经验总结。实际背景:MCU与一款芯片使用spi通信,MCU作为主机,芯片作为从机。这款芯片采用的是它规定的六线SPI,多了两根线:RDY和INT,这样从机就可以主动请求主机给主机发送数据了。一、问题描述根据从机芯片手
文章目录git常用命令(简介,详细参数往下看)Git提交代码步骤gitpullgitstatusgitaddgitcommitgitpushgit代码冲突合并问题方法一:放弃本地代码方法二:合并代码常用命令以及详细参数gitadd将文件添加到仓库:gitdiff比较文件异同gitlog查看历史记录gitreset代码回滚版本库相关操作远程仓库相关操作分支相关操作创建分支查看分支:gitbranch合并分支:gitmerge删除分支:gitbranch-ddev查看分支合并图:gitlog–graph–pretty=oneline–abbrev-commit撤消某次提交git用户名密码相关配置g