背包6讲(例题)

Dp是一个经典算法,而背包问题在dp中是属于dp里的经典;

所以搞dp就是经典中的经典!

现在我这里提供经典的6种背包型dp和具体实现:

1、采药(原版01)https://www.luogu.org/problem/show?pid=1048

n株草药,每株有一个价值和采集时间。最多采集m个单位时间。求最大价值,n≤100,m≤1000

题解:这是经典的01背包问题,01背包是最最简单的一种背包~~

我们用f[i,j]表示前i种物品在j的时间内能够得到的最大价值,那么最最简单的f[0,0]=0

这我们称之为dp的边界!!!

对于一般的f[i,j],考虑第i个物品不选,那么f[i,j]=f[i-1,j];

考虑第i个物品选,那么f[i-1][j-v[i]]+w[i]该算式的实质是我们以v[i]为代价,换取w[i]的利润(好处)

综上,对于一般的经典01背包问题的经典递推式是:f[i,j]=max(f[i-1,j],f[i-1,j-v[i]]+w[i])  边界是:f[0,0]=0;
pascal实现:

program O1Pack;
uses math;
var n,m,i,j,ans:longint;
    t,c:array[0..1000]of longint;
    f:array[0..1000,0..1000]of longint;
begin
  readln(m,n);
  for i:=1 to n do readln(t[i],c[i]);
  fillchar(f,sizeof(f),0);
  for i:=1 to n do
    for j:=0 to m do begin
      f[i,j]:=f[i-1,j];
      if j>=t[i]
        then f[i,j]:=max(f[i,j],f[i-1,j-t[i]]+c[i]);
    end;
  ans:=0;
  for i:=0 to m do
    ans:=max(ans,f[n,i]);
  writeln(ans);
end.

优化:滚动数组优化到1维空间:

uses math;
var i,j,v,n:longint;
    c,w:array[1..111]of longint;
    f:array[0..1111]of longint;
begin
 readln(V,N);
 for i:=1 to n do readln(w[i],c[i]);
 for i:=1 to n do
   for j:=V downto w[i] do
     f[j]:=max(f[j],f[j-w[i]]+c[i]);
 writeln(f[V]);
end.

2、采药(完全背包)

n种草药,每种有价值、采集时间,同种草药采集次数不限。最多采集M个单位时间。求最大价值。

n≤100,M≤1000

:f[i][j]表示前i株中采集,用j个单位时间,最大价值。

f[i][j]=max(f[i-1][j],f[i][j-t[i]]+v[i])

uses math;
Const maxn=10000;maxM=100000;
var M,n,j,i:longint;
    w,v: array[1..maxn] of longint;
    F:array[0..maxM] of longint; //用滚动数组
begin
  read(M,n);
  for i:= 1 to n do read(w[i],v[i]);
  for i:=1 to n do
   for j:=w[i] to M do
     F[j]:=max(F[j],F[j-w[i]]+v[i]);
   writeln(F[M]);
end.

3、采药(二维限制背包)

n种草药,每种有价值、采集时间和采集次数限制。最多采集M个单位时间。求最大价值。(每株只能采摘一次)

n≤100,M≤1000

解:
f[i,j]前i珠中花费时间小于等于j的草药的最大价值
w[]表示花费时间
c[]表示草药价值
t[]表示采摘次数
想到枚举k表示采集草药的次数来转移
f[i,j]=max(f[i-1,j],t);
t=max{f[i-1][j-k*w[i]]+k*c[i]}(0<=k<=t[i])
于是动态转移方程为
f[i,j]=max(f[i-1,j],=max{f[i-1][j-k*w[i]]+k*c[i]})(0<=k<=t[i]);

uses math;
var n,m,i,j,k:longint;
    w,c,t:array[1..10000]of longint;
    f:array[0..100,0..1000]of longint;
begin
 readln(n,m);
 for i:=1 to n do readln(w[i],c[i],t[i]);
 for i:=1 to n do
  for j:=1 to m do
   for k:=0 to t[i] do
    if j>=k*w[i] then
    f[i,j]:=max(f[i-1,j],f[i-1,j-k*w[i]]+c[i]*k)
    else f[i,j]:=f[i-1,j];
 writeln(f[n,m]);
end.

 4、采药(二维费用) T5056
n株草药,每株有价值c[]、采集时间w[]、体力消耗p[]。最多采集M个单位时间,最多消耗t点体力值。求最大价值。
n≤100,M≤300,t≤300(每一件只能选一样或者每一件能选无数多)
input

5 100 200
10 20 30
5 25 35
50 70 100
1 1 1
125 25 4

output

166(300)

每一件只能选一次:

uses math;
var n,m,t,i,j,k:longint;
    w,p,c:array[1..100000]of longint;
    f:array[0..100,0..300,0..300]of longint;
begin
 readln(n,m,t);
 for i:=1 to n do readln(w[i],p[i],c[i]);
 for i:=1 to n do
  for j:=0 to m do
   for k:=0 to t do begin
    f[i,j,k]:=f[i-1,j,k];
    if (w[i]<=j)and(p[i]<=k) then
    f[i,j,k]:=max(f[i,j,k],f[i-1,j-w[i],k-p[i]]+c[i]);
   end;
 writeln(f[n,m,t]);
end.

每一件可以选任意多:

uses math;
var n,m,t,i,j,k:longint;
    w,p,c:array[1..100000]of longint;
    f:array[0..100,0..300,0..300]of longint;
begin
 readln(n,m,t);
 for i:=1 to n do readln(w[i],p[i],c[i]);
 for i:=1 to n do
  for j:=0 to m do
   for k:=0 to t do begin
    f[i,j,k]:=f[i-1,j,k];
    if (w[i]<=j)and(p[i]<=k) then
    f[i,j,k]:=max(f[i,j,k],f[i,j-w[i],k-p[i]]+c[i]);
   end;
 writeln(f[n,m,t]);
end.

5、分组背包 L1757
n株草药,每株有价值c[]、采集时间w[]、草药类别t[]。最多采集M个单位时间。相同类别的草药只能采一株,求最大价值。
n≤100,M≤1000

定义状态:f[i][j]表示在前i类中采集,用j单位时间的最大价值
状态转移:k枚举每一类,j枚举时间,i枚举每一类中的每一元素
为了方便起见,我们在处理类别的时候仿造邻接矩阵来存,a[t,0]为长度,a[t,i]为t种草药的第i号
状态转移与01背包有相似的地方:
选第k类的if[k-1,i]

采药⑹ 01背包输出方案问题
n株草药,每株有一个价值和采集时间。最多采集M个单位时间。求最大价值,输出任意一种方案。
n≤100,M≤1000
  解:f[i][j]表示前i株中采集,用j个单位时间,最大价值。
f[i][j]=max(f[i-1][j],f[i-1][j-t[i]]+v[i])
g[i][j]表示g[i][j]是从f[i-1][j]还是f[i-1][j-t[i]]+v[i]转移过来。
从g[n][m]倒推回去,求出方案。

uses math;
var n,m,i,j:longint;
    f:array[0..1000,0..1000]of longint;
    g:array[0..1000,0..1000]of boolean;
    w,c:array[0..10000]of longint;
begin
 //g[i,j](bool) means f[i,j] change
 //by f[i-1,j] or f[i-1,j-t[i]]+v[i]?
 readln(m,n);
 for i:=1 to n do readln(w[i],c[i]);
 fillchar(g,sizeof(g),false);
 //false means change by f[i-1,j];
 for i:=1 to n do
  for j:=1 to m do begin
  f[i,j]:=f[i-1,j];
  if w[i]<=j then
   if f[i,j]<f[i-1,j-w[i]]+c[i] then begin
    f[i,j]:=f[i-1,j-w[i]]+c[i];
    g[i,j]:=true;
   end;
 end;
 writeln(f[n,m]);
 while (m>0)and(n>0) do begin
  if g[n,m]then begin
   write(n,' ');
   m:=m-w[n];
   dec(n);
  end
  else begin dec(n); end;
 end;
 writeln;
end.



原文地址:https://www.cnblogs.com/ljc20020730/p/7200039.html