Week8
动态规划2
8-1 疯狂的采药
题目链接
解题思路
完全背包的板子题,用一维数组时注意两层循环的先后和背包循环是正序的。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
vector<ll> v,w,f;
int t,n;
int main(){
cin>>t>>n;
f.resize(t+1,0);
w.push_back(0);
v.push_back(0);
for (int i=1; i<=n; i++){
int v1,w1;
scanf("%d%d",&w1,&v1);
w.push_back(w1);
v.push_back(v1);
}
for (int i=1; i<=n; i++){
for (int j=w[i]; j<=t; j++){
f[j]=max(f[j],f[j-w[i]]+v[i]);
}
}
cout<<f[t]<<endl;
return 0;
}
8-2 樱花
题目链接
解题思路
完全背包和多重背包的结合,因此处理时可以分成两部分。如果是完全背包,则内部正序循环即可;如果是多重背包,则需要先用二进制拆分打包成不同的组,然后再将这些组视为01背包处理即可。
#include<bits/stdc++.h>
using namespace std;
int m,n;
struct node{
int w,v,b;
};
vector <node> a;
vector <int> f;
int main(){
int t1,t2,t3,t4;
char c;
cin>>t1>>c>>t2>>t3>>c>>t4>>n;
if (t4<t2){
t4=t4+60-t2;
t3--;
}
else {
t4-=t2;
}
t3-=t1;
m=t3*60+t4;
for (int i=0; i<n; i++){
int w,v,p;
scanf("%d%d%d",&w,&v,&p);
if (!p){
a.push_back({w,v,0});
}
else {
for (int j=1; j<=p; j*=2){
p-=j;
a.push_back({j*w,j*v,1});
}
if (p>0){
a.push_back({p*w,p*v,1});
}
}
}
f.resize(m+1);
for (auto i:a){
if (!(i.b)){
for (int j=i.w; j<=m; j++){
f[j]=max(f[j],f[j-i.w]+i.v);
}
}
else {
for (int j=m; j>=i.w; j--){
f[j]=max(f[j],(f[j-i.w]+i.v));
}
}
}
cout<<f[m];
return 0;
}
8-3 摆花
题目链接
解题思路
可以视为01背包的变式,这里不是求一定容量的背包可拿到的最大价值,而是求塞满一定容量背包的方案数,所以需要f[i][j]+=f[i-1][j-l]
来实现不断累加,注意区分和f[i][j]=min(f[i-1][j-l]+1,f[i][j])
类型题目(这种一般是去求所有能填满背包的方案数中,所用物品数量最少的方案)的区别。
特别要注意初始化的处理,这里不仅仅是f[0][0]=1
,而是f[i][0]=1
,这样才能正确推到。从逻辑上也能讲清楚,i
种花摆0
个,有且只有一种情况,就是都不选。有时候初始化难度比推导递推式难度还大
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int n,m;
const int maxn=1e2+5;
const int MOD=1e6+7;
ll a[maxn];
ll f[maxn][maxn];
int main(){
cin>>n>>m;
for (int i=1; i<=n; i++){
cin>>a[i];
}
for (int i=0; i<=n; i++){
f[i][0]=1;
}
for (int i=1; i<=n; i++){
for (int j=m; j>=1; j--){
for (int l=0; l<=a[i]; l++){
if (j>=l)
f[i][j]+=f[i-1][j-l];
f[i][j]%=MOD;
}
}
}
cout<<f[n][m];
}
8-4 金明的预算方案
题目链接
解题思路
一定要注意到每个主件可以有 0 个、1个或 2 个附件
,这是本题突破口,如果附件数可以更多,那么算法就完全不同了。
因为最多仅两个附件,所以可以考虑归并物品数来实现01背包或者多重背包。
#include<bits/stdc++.h>
using namespace std;
struct node{
int w,v;
};
const int maxn=65;
int m,n;
const int maxn2=3.2e4+5;
node a[maxn][5],b[maxn][5];
int cnt[maxn],t[maxn];
int f[maxn][maxn2];
int main(){
cin>>m>>n;
for (int i=1; i<=n; i++){
int w,v,k;
scanf("%d%d%d",&w,&v,&k);
if (!k){
cnt[i]++;
a[i][cnt[i]].w=w;
a[i][cnt[i]].v=v;
} else {
cnt[k]++;
a[k][cnt[k]].w=w;
a[k][cnt[k]].v=v;
}
}
int num=0;
for (int i=1; i<=n; i++){
if (cnt[i]){
num++;
if (cnt[i]==1){
b[num][1].v=a[i][1].v*a[i][1].w;
b[num][1].w=a[i][1].w;
t[num]=1;
}
else if (cnt[i]==2){
b[num][1].v=a[i][1].v*a[i][1].w;
b[num][1].w=a[i][1].w;
b[num][2].v=a[i][1].v*a[i][1].w+a[i][2].v*a[i][2].w;
b[num][2].w=a[i][1].w+a[i][2].w;
t[num]=2;
}
else if (cnt[i]==3){
b[num][1].v=a[i][1].v*a[i][1].w;
b[num][1].w=a[i][1].w;
b[num][2].v=a[i][1].v*a[i][1].w+a[i][2].v*a[i][2].w;
b[num][2].w=a[i][1].w+a[i][2].w;
b[num][3].v=a[i][1].v*a[i][1].w+a[i][3].v*a[i][3].w;
b[num][3].w=a[i][1].w+a[i][3].w;
b[num][4].v=a[i][1].v*a[i][1].w+a[i][2].v*a[i][2].w+a[i][3].v*a[i][3].w;
b[num][4].w=a[i][1].w+a[i][2].w+a[i][3].w;
t[num]=4;
}
}
}
for (int i=1; i<=num; i++){
for (int j=1; j<=m; j++){
for (int l=0; l<=t[i]; l++){
if (j>=b[i][l].w){
f[i][j]=max(f[i][j],f[i-1][j-b[i][l].w]+b[i][l].v);
}
}
}
}
cout<<f[num][m];
return 0;
}
8-5 砝码称重
题目链接
解题思路
对于每个砝码,都有三种情况,可以视为[j]
[j+a[i]]
[abs(j-a[i])]
,然后用01背包。
注意:maxm=2e5+5
,要比题目要求的背包大小大一倍,因为推导的过程中可能会超过其大小。枚举背包容量的循环,注意要从0开始,否则会有遗漏。
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e2+5,maxm=2e5+5;
int n;
int a[maxn];
int sum;
bool f[maxn][maxm];
int main(){
cin>>n;
for (int i=1; i<=n; i++){
cin>>a[i];
sum+=a[i];
}
f[0][0]=1;
for (int i=1; i<=n; i++){
for (int j=0; j<=sum; j++){
f[i][j]=f[i-1][abs(j-a[i])] || f[i-1][j] || f[i-1][j+a[i]];
}
}
int ans=0;
for (int i=1; i<=sum; i++){
ans+=f[n][i];
}
cout<<ans;
return 0;
}
8-6 最大食物链计数
题目链接
解题思路
整体上拓扑排序的思想,定义两个数组分别去记录一个节点的入度数和出度数。先将入度数为0的结点加入队列,对于该结点所连的所有子节点都需要先更新f[i],再分别将入度数减一,最后新产生的入度为0的结点入队列。最后扫描的时候,累加出度数为0的结点数即可。
#include<bits/stdc++.h>
using namespace std;
#define MOD 80112002
typedef long long ll;
const int maxn=5e3+5,maxm=5e5+5;
int n,m;
vector <int> a[maxn];
int in[maxn],out[maxn];
queue <int> q;
ll f[maxn];
int main(){
cin>>n>>m;
for (int i=1; i<=m; i++){
int u,v;
scanf("%d%d",&u,&v);
a[u].push_back(v);
in[v]++;
out[u]++;
}
for (int i=1; i<=n; i++){
if (in[i]==0){
f[i]=1;
q.push(i);
}
}
while (!q.empty()){
int top=q.front();
q.pop();
int l=a[top].size();
for (int i=0; i<l; i++){
int to=a[top][i];
f[to]+=f[top];
f[to]%=MOD;
in[to]--;
if (in[to]==0){
q.push(to);
}
}
}
ll ans=0;
for (int i=1; i<=n; i++){
if (out[i]==0){
ans=(ans+f[i])%MOD;
}
}
cout<<ans%MOD;
return 0;
}