YbtOJ 「动态规划」第1章 背包问题
不是很想开 dfs,来写点 dp 愉悦身心(bushi
例题1.采药问题
01 背包板子。写了滚动数组优化。
code
#include<bits/stdc++.h>
using namespace std;
const int N=105;
int t,m,w[N],c[N],f[N*10];
int main()
{
scanf("%d%d",&t,&m);
for(int i=1;i<=m;i++) scanf("%d%d",&w[i],&c[i]);
for(int i=1;i<=m;i++)
for(int j=t;j;j--)
if(j-w[i]>=0) f[j]=max(f[j],f[j-w[i]]+c[i]);
cout<<f[t]<<endl;
return 0;
}
例题2.货币系统
关键性质:B 集合属于 A,且 A 中能被集合内其他数构成的数不在 B 中。问题转化为判断 A 中哪些数能被其他数构成,这就是个完全背包板子了。
code
#include<bits/stdc++.h>
#define G() cr=getchar()
using namespace std;
int p,n;
int main()
{
scanf("%d",&p);
while(p--)
{
int ans=0;
scanf("%d",&n);
int a[101]={0},b[25001]={0};
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
b[a[i]]=1;
}
sort(a+1,a+n+1);
for(int i=1;i<=25000;i++)
{
if(!b[i]) continue;
for(int j=1;j<=n;j++)
{
if(i+a[j]<=a[n]) b[i+a[j]]=2;
}
}
for(int i=1;i<=25000;i++) if(b[i]==1) ans++;
cout<<ans<<endl;
}
return 0;
}
例题3.宝物筛选
二进制拆分优化多重背包。依然是板子。(可能是个奇奇怪怪的二进制拆分写法/kel
code
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5;
int n,t,c[N],w[N];
int f[N],cnt;
int main()
{
scanf("%d%d",&n,&t);
for(int i=1,W,C,m;i<=n;i++)
{
scanf("%d%d%d",&C,&W,&m);
int j;
for(j=0;(1<<(j+1))-1<=m;j++)
{
w[++cnt]=(1<<j)*W,c[cnt]=(1<<j)*C;
}
w[++cnt]=W*(m-((1<<j)-1)),c[cnt]=C*(m-((1<<j)-1));
}
//for(int i=1;i<=cnt;i++) cout<<w[i]<<" "<<c[i]<<endl;
for(int i=1;i<=cnt;i++)
for(int j=t;j;j--)
if(j-w[i]>=0) f[j]=max(f[j],f[j-w[i]]+c[i]);
cout<<f[t]<<endl;
return 0;
}
例题4.硬币方案
直接用上一道题代码魔改。把重量设为硬币面值,价值随便设个数,背包容量为 \(m\)。
把 \(f\) 数组初始值设为负无穷,则 \(f_j\) 求出来的就是重量和正好为 \(j\) 时的价值。
最后统计数组里有多少个不是负无穷即可。
code
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5;
int n,t,c[N],w[N];
int f[N],cnt;
int aa[N],cc[N];
int main()
{
while(1)
{
scanf("%d%d",&n,&t);
if(n==0&&t==0) break;cnt=0;
for(int i=1;i<=n;i++) scanf("%d",&aa[i]);
for(int i=1;i<=n;i++) scanf("%d",&cc[i]);
for(int i=1;i<=n;i++)
{
//scanf("%d%d%d",&C,&W,&m);
int j,W=aa[i],C=1,m=cc[i];
for(j=0;(1<<(j+1))-1<=m;j++)
{
w[++cnt]=(1<<j)*W,c[cnt]=(1<<j)*C;
}
w[++cnt]=W*(m-((1<<j)-1)),c[cnt]=C*(m-((1<<j)-1));
}
for(int i=0;i<=t;i++) f[i]=-2e9;f[0]=0;
//for(int i=1;i<=cnt;i++) cout<<w[i]<<" "<<c[i]<<endl;
for(int i=1;i<=cnt;i++)
for(int j=t;j;j--)
if(j-w[i]>=0) f[j]=max(f[j],f[j-w[i]]+c[i]);
int ans=0;
for(int i=1;i<=t;i++) if(f[i]>=0) ans++;
cout<<ans<<endl;
}
return 0;
}
例题5.金明的预算方案
一个物品只有两个附件。dp 过程分类讨论:
- 只选主件
- 主件和附件1
- 主件和附件2
- 三个都选
其余和 01 背包板子相同。
code
#include<bits/stdc++.h>
#define G() cr=getchar()
using namespace std;
int xr;char cr;
int w1[61],v1[61],v2[61][3],w2[61][3],f[32001],m,n,p,q,v;
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
{
scanf("%d%d%d",&v,&p,&q);
if(q)
{
w2[q][0]++;
w2[q][w2[q][0]]=v;
v2[q][w2[q][0]]=p*v;
}
else w1[i]=v,v1[i]=v*p;
}
for(int i=1;i<=m;i++)
{
for(int j=n;j>=w1[i];j--)
{
f[j]=max(f[j],f[j-w1[i]]+v1[i]);
if(w1[i]+w2[i][1]<=j) f[j]=max(f[j],f[j-w1[i]-w2[i][1]]+v1[i]+v2[i][1]);
if(j>=w1[i]+w2[i][2]) f[j]=max(f[j],f[j-w1[i]-w2[i][2]]+v1[i]+v2[i][2]);
if(j>=w1[i]+w2[i][1]+w2[i][2])
f[j]=max(f[j],f[j-w1[i]-w2[i][1]-w2[i][2]]+v1[i]+v2[i][1]+v2[i][2]);
}
}
cout<<f[n]<<endl;
return 0;
}
1.求好感度
依然是多重背包板子。说实话没看出来和例题 3 有啥区别。
code
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5;
int n,t,c[N],w[N];
int f[N],cnt;
int main()
{
scanf("%d%d",&n,&t);
for(int i=1,W,C,m;i<=n;i++)
{
scanf("%d%d%d",&m,&C,&W);
int j;
for(j=0;(1<<(j+1))-1<=m;j++)
{
w[++cnt]=(1<<j)*W,c[cnt]=(1<<j)*C;
}
w[++cnt]=W*(m-((1<<j)-1)),c[cnt]=C*(m-((1<<j)-1));
}
//for(int i=1;i<=cnt;i++) cout<<w[i]<<" "<<c[i]<<endl;
for(int i=1;i<=cnt;i++)
for(int j=t;j;j--)
if(j-w[i]>=0) f[j]=max(f[j],f[j-w[i]]+c[i]);
cout<<f[t]<<endl;
return 0;
}
2.购买商品
其实不用像书上一样 dfs。不好写而且时间复杂度高。
先对 A 物品做 01 背包,并把数组初始值设为负无穷。这样,找到 f 数组中最大的 \(f_i\),\(i\) 就是取最大值时花的钱。
再用剩下的钱对 B 做完全背包就好了。
时间复杂度 \(O(T(n+m))\)。
code
#include<bits/stdc++.h>
using namespace std;
const int N=1000005;
int t,m,wa[N],ca[N],wb[N],cb[N],n;
int f[N];
int main()
{
scanf("%d%d",&t,&n);
for(int i=1;i<=n;i++) scanf("%d%d",&wa[i],&ca[i]);
scanf("%d",&m);
for(int i=1;i<=m;i++) scanf("%d%d",&wb[i],&cb[i]);
for(int i=1;i<=t;i++) f[i]=-2e9;
for(int i=1;i<=n;i++)
for(int j=t;j;j--)
if(j>=wa[i]) f[j]=max(f[j],f[j-wa[i]]+ca[i]);
int maxn=-1,mon=0;
for(int i=0;i<=t;i++)
{
if(f[i]>maxn) maxn=f[i],mon=i;
}
t-=mon;
for(int i=1;i<=t;i++) f[i]=0;
for(int i=1;i<=m;i++)
for(int j=1;j<=t;j++)
if(j>=wb[i]) f[j]=max(f[j],f[j-wb[i]]+cb[i]);
cout<<f[t]<<endl;
return 0;
}
3.魔法开锁
每个箱子向它所装钥匙对应箱子连边建图。则这个图每个点的入度出度均为 \(1\),即由若干个互不相交的环构成。
当每个环都有点被选时,能打开所有箱子。设 \(f_{i,j}\) 表示前 \(i\) 个环,共选了 \(j\) 个点时的方案数。
转移见代码。(懒得打 LaTeX
注意每个环至少选一个点,\(k\) 枚举时不带等号。
code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=305;
int T,n,t;
int a[N],fa[N];
double f[N][N];
double c[N][N];
int find(int x)
{
if(fa[x]==x) return x;
else return fa[x]=find(fa[x]);
}
void init()
{
c[0][0]=1;
for(int i=1;i<=300;i++)
{
c[i][0]=1;
for(int j=1;j<=i;j++) c[i][j]=c[i-1][j-1]+c[i-1][j];//cout<<i<<" "<<j<<" "<<c[i][j]<<endl;
}
}
int tt[N],cnt[N],tot;
signed main()
{
scanf("%lld",&T);
init();
//printf("%.0lf\n",c[300][99]);
while(T--)
{
scanf("%lld%lld",&n,&t);
for(int i=1;i<=n;i++) fa[i]=i;
for(int i=1;i<=n;i++)
{
tt[i]=cnt[i]=tot=0;
scanf("%d",&a[i]);
//cout<<find(a[i])<<" "<<find(i)<<endl;
fa[find(a[i])]=find(i);
}
//for(int i=1;i<=n;i++) cout<<find(i)<<" ";
for(int i=1;i<=n;i++) tt[find(i)]++;
for(int i=1;i<=n;i++) if(tt[i]) cnt[++tot]=tt[i];
for(int i=0;i<=tot;i++) for(int j=0;j<=t;j++) f[i][j]=0;
f[0][0]=1;
for(int i=1;i<=tot;i++)
for(int j=0;j<=t;j++)
for(int k=0;k<j;k++)
{
f[i][j]+=f[i-1][k]*c[cnt[i]][j-k];
//cout<<cnt[i]<<" "<<j-k<<endl;
//cout<<i<<" "<<j<<" "<<k<<" "<<f[i][j]<<" "<<f[i-1][k]<<" "<<c[cnt[i]][j-k]<<endl;
}
double ans1=f[tot][t],ans2=c[n][t];
printf("%.6lf\n",ans1/ans2);
}
return 0;
}
4.购买礼物
为什么网上搜不到这题题解啊,书上代码给得跟没给一样/kel
设 \(f[i][j][k][0/1]\) 表示前 \(i\) 个物品,第一张卡花了 \(j\) 元,第二张卡花了 \(k\) 元,当前没用/用了免费卡。
状态设计出来了转移应该不难想。但是这题转移比较复杂,挂了一定多读几遍代码确认一下是不是手残问题()
关于判无解:\(f\) 数组初始值负无穷,看最后答案是否大于 \(0\) 即可。
其它奇奇怪怪的判无解方式会挂,显然本喵把所有的坑都踩了一遍挂了 2h...
code
#include<bits/stdc++.h>
using namespace std;
const int N=505;
int a,b,n,sum;
int w[N],c[N],s[N],flag[N];
int f[305][505][55][2];
int main()
{
scanf("%d%d%d",&a,&b,&n);
for(int i=1;i<=n;i++) scanf("%d%d%d",&w[i],&c[i],&s[i]);
for(int i=0;i<=n;i++)
for(int j=0;j<=a;j++)
for(int k=0;k<=b;k++)
f[i][j][k][0]=f[i][j][k][1]=-2e9;
f[0][0][0][0]=f[0][0][0][1]=0;
for(int i=1;i<=n;i++)
{
for(int j=0;j<=a;j++)
{
for(int k=0;k<=b;k++)
{
if(s[i]==0)
{
f[i][j][k][0]=f[i-1][j][k][0];//不买
f[i][j][k][1]=f[i-1][j][k][1];
}
if(j>=w[i]) f[i][j][k][0]=max(f[i][j][k][0],f[i-1][j-w[i]][k][0]+c[i]),flag[i]=1;//第一张卡
if(k>=w[i]) f[i][j][k][0]=max(f[i][j][k][0],f[i-1][j][k-w[i]][0]+c[i]),flag[i]=1;//第二张卡
//cout<<i<<" "<<j<<" "<<k<<" "<<f[i][j][k][0]<<endl;
f[i][j][k][1]=max(f[i][j][k][1],f[i-1][j][k][0]+c[i]);//免费
if(j>=w[i]) f[i][j][k][1]=max(f[i][j][k][1],f[i-1][j-w[i]][k][1]+c[i]),flag[i]=1;//第一张卡
if(k>=w[i]) f[i][j][k][1]=max(f[i][j][k][1],f[i-1][j][k-w[i]][1]+c[i]),flag[i]=1;//第二张卡
}
}
}
int maxn=-2e9;
for(int i=0;i<=a;i++)
for(int j=0;j<=b;j++)
maxn=max(maxn,f[n][i][j][0]),maxn=max(maxn,f[n][i][j][1]);
if(maxn<0) cout<<"-1"<<endl;
else cout<<maxn<<endl;
return 0;
}
5.课题选择
第三题简化版。
设 \(f_{i,j}\) 表示前 \(i\) 个课题共选了 \(j\) 篇所需的最小时间。
转移方程为 \(f_{i,j}=\min \{f_{i-1,k}+A_i\times (j-k)^{B_i}\}\)。
code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=205;
int n,m,a[N],b[N];
int f[N][N];
int qpow(int n,int k)
{
int ans=1;
while(k)
{
if(k&1) ans*=n;
n*=n;k>>=1;
}
return ans;
}
signed main()
{
scanf("%lld%lld",&n,&m);
for(int i=1;i<=m;i++) scanf("%lld%lld",&a[i],&b[i]);
for(int i=0;i<=max(n,m);i++) for(int j=0;j<=max(n,m);j++) f[i][j]=2e9;
f[0][0]=0;
for(int i=1;i<=m;i++)
for(int j=0;j<=n;j++)
for(int k=0;k<=j;k++)
f[i][j]=min(f[i][j],f[i-1][k]+a[i]*qpow(j-k,b[i]));
cout<<f[m][n]<<endl;
return 0;
}
本文来自博客园,作者:樱雪喵,转载请注明原文链接:https://www.cnblogs.com/ying-xue/p/16588014.html