最近按宇神的blog学了学背包,写了几道题,现在做一下总结。。。。。
(学的较浅,如有不对,还请指正 Q A Q)
首先是背包的分类:A . 0 1 背包 ,完全背包,多重背包;
先学了01背包和完全背包
分别来解释一下两种背包的含义:
{
01背包:有一个总容量为M的背包要放进N个物品,物品重量为d,物品的价值为V,每个物品只能放一次,问放哪些物品可以使放的物品总价值最大;
完全背包:有一个容量为M的背包,有N种物品,每种物品有无限个,问怎么放可以使背包总价值最大;
}
考虑01背包:每个物品只能有两种选择--->放或者不放,(这也是01背包名称的由来,0代表不放,1代表放)
那么第 i 个物品的选择不同就会导致背包状态的不同:用F[N,M]函数来表示背包的总价值,放第i个物品时背包的价值
F[i,m]=max{F[i-1,m],F[i-1,m-d[i]]+v[i]},这个状态转移方程的意义为轮到放入第i个物品时的价值由不放这个物品或者放了这 个 物品的价值这两者的价值最大者决定;所以说第i个物品的状态由上一个物品决定。
于是我们定义一个数组dp,用dp[j]来表示背包物品总重量为j的价值,从M到的d[i]遍历一下,就可以得到结果;
POINT:1.dp[j]=max(dp[j],dp[j-d[i]]+v[i]) (状态转移方程);
2.由于每个物品只能拿一次,所以为了不重复拿物品,第二层循环要倒序;
(解释POINT2)例子:
下面是01背包的模板:
#include<cstdio>
const int maxn=100;
int dp[maxn];
int d[maxn],v[maxn];
int main()
{
cin>>n>>m;
for(int i=0;i<n;i++)
scanf("%d %d",&d[i],&v[i]);
for(int i=0;i<n;i++)
for(int j=m;j>=d[i];j--)//倒序是保证i物品的状态是由i-1推出,不会受到i-2,i-3...物品的影响
dp[j]=max(dp[j],dp[j-d[i]]+v[i]);
return 0;
}
完全背包:由定义我们可以知道,每个物品可以拿多次,所以我们直接正序遍历就行了;其他和01背包一样;
状态转移方程也是一样;
下面是完全背包的模板:
#include<cstdio>
const int maxn=100;
int dp[maxn];
int d[maxn],v[maxn];
int main()
{
cin>>n>>m;
for(int i=0;i<n;i++)
scanf("%d %d",&d[i],&v[i]);
for(int i=0;i<n;i++)
for(int j=a[i];j<=m;j++)
dp[j]=max(dp[j],dp[j-d[i]]+v[i]);
return 0;
}
关于dp数组初始化问题;
一般关于背包的问题有两种问法:恰好装满背包,没有要求恰好装满,第一种问法的话,那么除了dp[0]=0以外,其他均为负无穷,这是因为初始时候只有背包为0有合法解0,其他没有;第二种的话都初始化为0,因为开始没有放进一个物品,所以都为0;
母函数:假如有三种硬币,面值分别为1,2,5,问组成价值为K的硬币有多少种方法;
每种硬币的数目为num1,num2,num3;
G(x)=(1+x+x^2+x^3+...x^num1)(1+x^2+x^4+...x^(2*num2))(1+x^5+x^10+...x^(10*num3));
那么运算完后,设x^n前面的系数为an,那么组成价值为n的钱数的组合数为an;
我们将x的幂当做价值,这样看的话,第一个多项式就是(x^0+x^1+x^2+....x^num1),意思为拿一个一元硬币,拿两个一元硬币,拿num1个硬币;剩下两个多项式同理,那么我们将其相乘,幂相加,再合并同类项,就能得到结果;
展开一下上面的多项式:G(x)=1+a1*x+a2*x^2+......an*x^n;
我们把G(x)称为序列a1,a2,a3....an的母函数;
这种求硬币的母函数为普通型母函数;
下面是普通型母函数的模板:
#include<cstdio>
#include<cstring>
const int maxn=100;
int c1[maxn],c2[maxn];
int cent[3]={1,2,5};
int M;//最大可能得到价值,M=num1+2*num2+5*num3;
int main()
{
cin>>M;
for(int i=0;i<=M;i++)
c1[i]=1;
memset(c2,0,sizeof(c2));
for(int i=0;i<3;i++)
{
for(int j=0;j<=M;j++)
for(int k=0;k+j<=M;k+=cent[i])
c2[k+j]+=c1[j];//中间数组记录一下
for(int j=0;j<=M;j++)
{
c1[j]=c2[j];
c2[j]=0;
}
}
for(int j=0;j<=M;j++)
printf("%d%c",c1[j],j==M?'\n':' ');
return 0;
}
还有一种母函数:指数型母函数;
有这样一个问题:有n个数,从这n个数选m个数,问有多少种不同的排列方法;
我们这样考虑,相当于选数,每个数可能出现mk次,由于问的是不同的排列方法,所以要除以阶乘;
比如1 1 2 5 和1 1 2 5 的排列方式是一样的,所以要去除重复的;
那么我们就可以写出他的多项式了:
G(x)=(1+x+x^2/(2!)+x^3/(3!)+....x^m1/(m1!))(1+x+x^2/(2!)+x^3/(3!)+....x^m2/(m2!))....(1+x+x^2/(2!)+x^3/(3!)+....x^mk/(mk!));
下面是其模板:
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int maxn=11;
LL f[maxn];
double c1[maxn],c2[maxn];
void fa()
{
f[0]=1;
for(int i=1;i<maxn;i++)
f[i]=i*f[i-1];
}
int main()
{
fa();
int n,m;
cin>>n>>m;
memset(c1,0,sizeof(c1));
memset(c2,0,sizeof(c2));
c1[0]=1;
for(int i=0;i<n;i++)
{
for(int j=0;j<m;j++)
for(int k=0;k<num&&k+j<m;k++)
c2[k+j]+=c1[j]/f[k];
for(int i=0;i<m;i++)
{
c1[i]=c2[i];
c2[i]=0;
}
}
for(int i=0;i<m;i++)
printf("%.01f%c",c1[i]*f[i],'\n':' ');
return 0;
}