一、本题的暴力解法dfs(只能过很少的点)
虽然剪枝了,但是对的点不多,大多数点都超时了。但是dfs的优点是很好写,如果没有思路的话,上手就能写,还可以拿来和自己的更优解法进行对拍(对拍可以检验你认为的更优解法的正确性)
这里贴上暴力代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define ll long long
const int maxn=1500;
ll a[maxn][maxn];
ll fei[maxn];
ll danjia[maxn][maxn];
ll vis[maxn];
using namespace std;
int n,m,k,y;
ll min_sum=1e9;
void dfs(ll cur,ll sum,ll kk){
if(sum>min_sum) return; //最优化剪枝
if(kk==k){
if(sum<min_sum) min_sum=sum;
return;
}
if(cur>n) return; //可行性剪枝
ll minj;
if(m<k-kk) minj=m; //可行性剪枝
else minj=k-kk;
for(ll i=cur;i<=n;i++){
for(ll j=1;j<=minj;j++){
if(!vis[i] && j<=(k-kk)){
vis[i]=1;
dfs(cur+1,sum+a[i][j],kk+j);
vis[i]=0; //这里回溯一下
}
}
}
}
int main(){
//freopen("in.txt","r",stdin); 调试时候习惯用文件
//freopen("out.txt","w",stdout);
cin>>n>>m>>k>>y;
for(int i=1;i<=n;i++) cin>>fei[i];
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++) cin>>a[i][j];
for(int i=1;i<=n;i++)
for(int j=1;j<y;j++) a[i][j]+=fei[i]*j;
/*for(int i=1;i<=n;i++){ //这里是一个检测点,用来输出一下更新之后的矩阵
for(int j=1;j<=m;j++) cout<<a[i][j]<<' ';
cout<<endl;
}*/
dfs(1,0,0);
cout<<min_sum;
return 0;
}
二、01背包问题
例题:信息学奥赛一本通评测网站 P1267 01背包问题
(感觉这个评测网站不错,对新手的话比较友好,前面有简单的语言题,也有一些基础算法的经典例题,还有各年noip的例题,做acm刷着也不错)。
题目描述很简单,01的意思就是要么装物品,要么不装物品,这其中每个物品只有一件。
下面的代码中的f[x]代表重量不超过x公斤的最大的价值,w代表每件物品的重量,c[i]代表每件物品的价值。那么在每次做出决定的时候,无非就涉及装或不装的选择。
装当前物品就是f[v-w[i]]+c[i],不装当前物品就是f[v];状态转移方程就是f[v]=max(f[v-w[i]]+c[i],f[v])
自己的理解,可以认为f[v-w[i]]就相当于是目前这个选择下,不装当前物品,也不装其它物品的情况(当然此时背包里面已经装了重量为v-w[i]的物品了),那再此基础上加上c[i]就相当于是在当前选择下装上当前物品的情况(这种情况下执行f[v]=f[v-w[i]]+c[i])。而f[v]就代表不选,也就是当前选择与之前的不发生变化(背包的东西没有任何变动,这种情况下执行f[v]=f[v])。显然我们要每次做出使得f[v]尽可能大的选择。
代码如下:
#include<iostream>
#include<cstdio>
using namespace std;
const int maxn=35,maxm=20000;
int main(){
int f[maxm]={0},w[maxn],c[maxn];
int m,n;
cin>>m>>n;
for(int i=1;i<=n;++i){
cin>>w[i]>>c[i];
}
for(int i=1;i<=n;i++) //从第一个开始往后选
for(int v=m;v>=w[i];v--){ //注意v在这里表示的是背包的剩余容量,所以一定是以从大到小的顺序 进行的
if(f[v-w[i]]+c[i]>f[v]){ //状态转移方程
f[v]=f[v-w[i]]+c[i];
}
}
cout<<f[m];
return 0;
}
三、完全背包问题
例题:信息学奥赛一本通评测网站 P1268 完全背包问题
完全背包不同于01背包的只是完全背包中,每种物品都有无限件可以用。那么问题就不是选不选,而是每种物品选几件的问题了。
题解代码:
#include<iostream>
#include<cstdio>
using namespace std;
const int maxn=35,maxm=20000;
int main(){
int f[maxm]={0},w[maxn],c[maxn];
int m,n;
cin>>m>>n;
for(int i=1;i<=n;++i){
cin>>w[i]>>c[i];
}
for(int i=1;i<=n;i++)
for(int v=w[i];v<=m;v++){
if(f[v-w[i]]+c[i]>f[v]){
f[v]=f[v-w[i]]+c[i];
}
}
cout<<"max=";
cout<<f[m];
return 0;
}
我们发现,完全背包问题和多重背包问题,只有一行代码的差异。这个时候,我们就要找01背包问题和完全背包问题的关联。01背包问题中,背包的剩余重量是从大到小推的,因为我们要保证每次的f[v]是要从之前的f[v]或者f[v-w[i]]+c[i]得到的,这也正保证了每种物品最多只装一件。(保证了每次选第i件物品时,绝不会依据一个已经选入过一次i物品的子结果)而对于完全背包,每种物品有无限件,因此在选第i件物品时,可以依据已经选入过任意次i物品的子结果)。因而需要倒过来进行枚举。
四、多重背包问题
例题:信息学奥赛一本通评测网站 P1269 庆功会
多重背包问题有两个解法,第一个是在完全背包的基础上,规定每个物品最多可选几个。
第二个解法是将其化为01背包问题(需要用到二进制的思想)
第一个解法(朴素解法)代码:
#include<iostream>
#include<cstdio>
#include<cmath>
using namespace std;
const int maxn=6005;
int n,m,x,y;
int n1=0,t=1;
int v[maxn],w[maxn],s[maxn],f[maxn];
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>v[i]>>w[i]>>s[i];
for(int i=1;i<=n;i++)
for(int j=m;j>=0;j--) //钱从多到少进行枚举
for(int k=0;k<=s[i];k++){ //这里就不同于完全背包了,而是有一个购买数量的限制s[i]
if(j-k*v[i]<0) break; //买不起
f[j]=max(f[j],f[j-k*v[i]]+k*w[i]); //状态转移方程增加了k
}
cout<<f[m];
return 0;
}
第二种解法(二进制优化解法):
```cpp
#include<iostream>
#include<cstdio>
#include<cmath>
using namespace std;
int main(){
const int maxn=6005;
int n,m,x,y,s;
int n1=0,t=1;
int v[maxn],w[maxn],f[maxn]={0};
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>x>>y>>s; //针对每一种商品,我们将其进行拆分
t=1;
while(s>=t){ //代表还能买,也就是数量大于1,就还要进行拆分
n1++; //把n个拆成n1个
v[n1]=x*t;
w[n1]=y*t;
s-=t;
t*=2; //每件物品最多取的件数s就可以用二进制表示
}
n1++;
v[n1]=x*s;
w[n1]=y*s;
}
//下面就化成了01背包问题
for(int i=1;i<=n1;i++)
for(int v1=m;v1>=v[i];v1--){
f[v1]=max(f[v1],f[v1-v[i]]+w[i]);
}
cout<<f[m];
return 0;
}
五、例题——能天使的愿望(dp正解)
原题是牛客挑战赛34的第一题
题解代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define ll long long
using namespace std;
const int maxn=1500;
const int inf=0x3f3f3f3f;
ll a[maxn][maxn];
ll fei[maxn];
ll vis[maxn];
ll f[maxn];
int n,m,k,y;
int main(){
cin>>n>>m>>k>>y; //这里的预处理是把邮费直接加在了价格里
for(int i=1;i<=n;i++) cin>>fei[i];
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++) cin>>a[i][j];
for(int i=1;i<=n;i++)
for(int j=1;j<y;j++) a[i][j]+=fei[i]*j;
for(int i=1;i<=n;i++){
for(int j=m+1;j<=k;j++) a[i][j]=inf; //为了防止选择不存在的商品
}
f[0]=0;
//f[x]代表购买x把铳的最小花费 ,可以把这个问题看做是多重背包问题。
for(int i=1;i<=k;i++)f[i]=inf; //首先假设都为无穷大,这样就可以挑选价格比无穷大小的商品
for(int i=1;i<=n;i++) //在每个商店进行挑选
for(int j=k;j>=1;j--) //还可以购买的数量从大到小(就像上一道题里面的拨款金额从大到小一样)
for(int kk=1;kk<=j;kk++) //每次购买的数量从小到大枚举,并且不能超过对应商店的最大购买量
f[j]=min(f[j],f[j-kk]+a[i][kk]); //这里选择的最小花费是不选择kk产品的最小花费和选择kk产品的最小花费
cout<<f[k];
return 0;
}
这个问题是一个典型的dp问题,在思路上很像背包问题,但是背包问题一般是求解最大收益,这个则是求解最小花费,涉及求最小的问题,就一定要预处理,将f数组从1到m的值置为无穷大(实际上是一个很大很大的数)。
六、作者
Bowen
作者水平有限,如有纰漏之处,敬请斧正。
欢迎大家一起学习交流。