模板:
多重背包二进制优化:
用于多重背包问题中,一个物品可以放限制次的数量。
主体代码:
在使用时,分别讨论a[i]*b[i]<=m,其中a[i]为数量,b[i]为权值,m为上限,这时候使用二进制优化,当a[i]*b[i]>m时,使用正常多重背包模板,这样写时间更优化。
同时,也可以在输入的过程中使用二进制优化
1.HDU2844 Coins(二进制优化)
#include
#include
#include
#include
using namespace std;
int n,m;
int dp[110000];
int a[1110],b[1110];
int main()
{
while(scanf("%d%d",&m,&n))
{
for(int i=0;i<n;i++)
scanf("%d%d",&a[i],&b[i]);
for(int i=0;i<n;i++)
{
if(a[i]*b[i]<=m)
{
for(int k=1;k<=a[i];k*=2)
{
for(int j=m;j>=k*b[i];j--)
{
if(j==k*b[i]||dp[j-k*b[i]])
dp[j]=1;
}
a[i]-=k;
}
if(a[i]>0)
{
for(int j=m;j>=a[i]*b[i];j--)
if(j==a[i]*b[i]||dp[j-a[i]*b[i]])
dp[j]=1;
}
}
else
{
for(int j=b[i];j<=m;j++)
if(j==b[i]||dp[j-b[i]])
dp[j]=1;
}
}
dp[0]=1;
for(int i=m;i>=0;i--)
if(dp[i])
{
cout<<i<<endl;
break;
}
}
return 0;
}
这里面的主题转移函数,也可以这样下写:
memset(dp,-0x7f,sizeof(dp));
dp[0]=0;
转移方程:
dp[j]=max(dp[j],dp[j-k*a[i]]+k);
2.POJ1276 二进制优化
#include
#include
#include
#include
using namespace std;
int n,m;
int dp[110000];
int a[15],b[15];
int main()
{
while(~scanf("%d%d",&m,&n))
{
memset(dp,0,sizeof(dp));
for(int i=0;i<n;i++)
scanf("%d%d",&a[i],&b[i]);
for(int i=0;i<n;i++)
{
if(a[i]*b[i]<=m)
{
for(int k=1;k<=a[i];k*=2)
{
for(int j=m;j>=k*b[i];j--)
{
if(j==k*b[i]||dp[j-k*b[i]])
dp[j]=1;
}
a[i]-=k;
}
if(a[i]>0)
{
for(int j=m;j>=a[i]*b[i];j--)
if(j==a[i]*b[i]||dp[j-a[i]*b[i]])
dp[j]=1;
}
}
else
{
for(int j=b[i];j<=m;j++)
if(j==b[i]||dp[j-b[i]])
dp[j]=1;
}
}
dp[0]=1;
for(int i=m;i>=0;i--)
if(dp[i])
{
cout<<i<<endl;
break;
}
}
return 0;
}
每个物品可以取无限次,有一定的空间限制,取最优
(同样也可以转化为0/1背包问题,使用二进制优化)
模板:
1.CF543A Writing Code
完全背包问题,转化为有多种物品,每个物品各有重量,可以放无限次,要求刚好放入n件物品,且重量小于m 的方案数
理解了dp[i][j]这个题目就好做了,表示已经写了i行代码产生j个bug数的方案数量
#include
#include
using namespace std;
int dp[550][550];
int a[550];
int n,m,b,mod;
int main()
{
scanf("%d%d%d%d",&n,&m,&b,&mod);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
dp[0][0]=1;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
for(int k=a[i];k<=b;k++)
{
dp[j][k]+=dp[j-1][k-a[i]];
dp[j][k]%=mod;
}
}
}
int cnt=0;
for(int i=0;i<=b;i++)
if(dp[m][i])
cnt+=dp[m][i],cnt%=mod;
cout<<cnt<<endl;
return 0;
}
2.CF189A Cut Ribbon
将一根长度为n的绳子分成只由a.b.c长度组成的子绳子。
完全背包问题 考虑到必须要能够正好由a.b.c拼凑出n
在完全背包中加入条件
if(dp[j-w[i]]!=0||j-w[i]==0)
dp[j]=max(dp[j],1+dp[j-w[i]]);
#include
#include
#include
using namespace std;
int n,a,b,c;
int dp[4500];
int max(int a,int b)
{
return a>b?a:b;
}
int main()
{
scanf("%d%d%d%d",&n,&a,&b,&c);
int w[3]={a,b,c};
for(int i=0;i<3;i++)
{
for(int j=w[i];j<=n;j++)
{
if(dp[j-w[i]]!=0||j-w[i]==0)
dp[j]=max(dp[j],1+dp[j-w[i]]);
}
}
cout<<dp[n]<<endl;
return 0;
}
3.HDU1114 Piggy-Bank
刚好完全背包问题
#include
#include
using namespace std;
int n,m;
int w0,w1;
int v[550],w[550];
int dp[11000];
int min(int a,int b)
{
return a<b?a:b;
}
int main()
{
int t;
cin>>t;
while(t--)
{
scanf("%d%d%d",&w0,&w1,&n);
m=w1-w0;
for(int i=0;i<n;i++)
scanf("%d%d",&v[i],&w[i]);
for(int i=1;i<=m;i++)
dp[i]=99999999;
dp[0]=0;
for(int i=0;i<n;i++)
{
for(int j=w[i];j<=m;j++)
{
if(j==w[i]||dp[j-w[i]])
dp[j]=min(dp[j-w[i]]+v[i],dp[j]);
}
}
if(dp[m]==99999999)
printf("This is impossible.\n");
else
printf("The minimum amount of money in the piggy-bank is %d.\n",dp[m]);
}
return 0;
}
模板:
三种写法:
一维数组:
for(int i=0;i<n;i++)
{
for(int j=m;j>=thing[i].first;j--)
{
dp[j]=max(dp[j],dp[j-thing[i].first]+thing[i].second);
}
}
二维数组:
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
if(j>=thing[i].first)
dp[i][j]=max(dp[i][j],dp[i-1][j-thing[i].first]+thing[i].second);
else
dp[i][j]=dp[i-1][j];
}
}
递归+记忆化:
int package(int index,int w)
{
if(index==0||w<=0)
return 0;
if(dp[index][w]!=0)
return dp[index][w];
//不放第index件物品
int res=package(index-1,w);
//放第index件物品
if(w>=thing[index].first)
res=max(res,package(index-1,w-thing[index].first)+thing[index].second);
dp[index][w]=res;
return res;
}
关于初始化的细节问题:
①要求恰好装满背包,那么在初始化时除了 F[0] 为 0,其它 F[1…V ] 均设为 −∞,这样就可以保证最终得到的 F[V ] 是一种恰好装满背包的最优解。
②如果并没有要求必须把背包装满,而是只希望价格尽量大,初始化时应该将 F[0…V ] 全部设为 0。
1.POJ3624 http://poj.org/problem?id=3624
一维DP数组
#include
#include
#include
using namespace std;
int n,m;
int dp[13000];
pair<int ,int > thing[3500];
int main()
{
cin>>n>>m;
for(int i=0;i<n;i++)
{
scanf("%d%d",&thing[i].first,&thing[i].second);
}
for(int i=0;i<n;i++)
{
for(int j=m;j>=thing[i].first;j--)
{
dp[j]=max(dp[j],dp[j-thing[i].first]+thing[i].second);
}
}
int ans=0;
for(int j=m;j>=0;j--)
ans=max(ans,dp[j]);
cout<<ans<<endl;
return 0;
}
二维DP数组:这种会超内存,就当个参考
#include
#include
#include
using namespace std;
int n,m;
int dp[3500][13000];
pair<int ,int > thing[3500];
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
{
scanf("%d%d",&thing[i].first,&thing[i].second);
}
/*for(int i=0;i=thing[i].first;j--)
{
dp[j]=max(dp[j],dp[j-thing[i].first]+thing[i].second);
}
}
int ans=0;
for(int j=m;j>=0;j--)
ans=max(ans,dp[j]);*/
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
if(j>=thing[i].first)
dp[i][j]=max(dp[i][j],dp[i-1][j-thing[i].first]+thing[i].second);
else
dp[i][j]=dp[i-1][j];
}
}
int ans=0;
for(int j=1;j<=m;j++)
{
ans=max(dp[n][j],ans);
}
cout<<ans<<endl;
return 0;
}