背包dp分类:
01背包dp
完全背包(飞扬的小鸟)
多重背包
二维费用背包
分组背包()
有依赖的背包(难的还是不好解决,虽说是在每一个依赖中做一次01背包,但感觉不好实现)
做背包问题最关键的就是找清楚并反问自己?这题里面 什么是容量? 什么是物品? 什么是物品的费用? 什么是物品的价值?
容量,就是这题当中我们怎样表示状态的数组。
费用,就是用来f【i】---->f【i+v【k】】,状态转移的跨度
价值,就是你这个dp的数组,所维护的东西。维护的数值!
例如下面这道题:求给所有的人安排房间的最小支出是多少? 一般这样问的,那么所有人在这里就表示整个dp的数组,多个房间就是物品,最小支出就是数组里存的价值。根据感觉,只要找出背包问题中的物品(也就是转移的方向)背包模型就好办了
vijos1240朴素的网络游戏
首先我们要明确,对于一道背包dp的题目来说,我们需要有容量,物品,费用,价值(权值,因为有些题要求最小),例如这个题:每一个房间能撑下多少人就是费用,因为最关键的我们一共要把所有的人乘下,才行。那么每加一个房间就会转移到下一个状况,更有,因为我们要求总价值最小,就是画的钱最少
vijos1037 搭建双塔
背包位运算的优化,bitset的实践
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<bitset>
using namespace std;
int w[105],n,h[105],sum=0;
int main()
{
scanf("%d",&n);
for (int i=1;i<=n;i++)scanf("%d",&w[i]),sum+=w[i];
bitset<3000> f[3000];
for (int i=0;i<=sum;i++) f[i].reset();
f[0].set(1);
for (int i=1;i<=n;i++)
{
for (int j=sum;j>=0;j--) if (f[j].any())
{
f[j+w[i]]=f[j]|f[j+w[i]];//位运算的关键
f[j]=f[j]|(f[j]<<w[i]);//关键,位移运算
}
}
int ans;
for (ans=sum;ans>0;ans--) if (f[ans].test(ans+1)) break;
if (ans) printf("%d",ans);else printf("Impossible");
return 0;
}
这是没有加位运算的,这样更直观一点,dp【i】【j】表示第一个塔高为i,第二个高为j的是否存在,输出时判断最大的i==j的输出
#include<cstdio>
#include<algorithm>
using namespace std;
int a[105],n;
bool dp[5000][5000];
int main()
{
int n;
scanf("%d",&n);
int sum=0;
for (int i=1;i<=n;i++) scanf("%d",&a[i]),sum+=a[i];
dp[0][0]=true;
for (int i=1;i<=n;i++)
{
for (int j=sum;j>=0;j--)
for (int l=sum;l>=0;l--)
if (dp[j][l])
{
dp[j+a[i]][l]=true;
dp[j][l+a[i]]=true;
}
}
int ans;
for(ans=sum;ans>0;ans--)if (dp[ans][ans]) break;
if (ans)printf("%d",ans);else printf("Impossible");
return 0;
}
vijos1198最佳课程选择(锻炼了一下,找背包的模型)这里用long long
#include<cstdio>
#include<cstring>
#include<string>
#include<cmath>
#include<iostream>
#define inf 0x7f7f7f7f
#define ll long long
using namespace std;
ll f[3090],a[550],b[550],n,m;
ll power(int a,int b)
{
ll ans=1;
while (b--) ans*=a;
return ans;
}
int main()
{
scanf("%lld%lld",&n,&m);
for (int i=1;i<=m;i++) scanf("%lld%lld",&a[i],&b[i]);
memset(f,inf,sizeof(f));
f[0]=0;
for (int i=1;i<=m;i++)
{
for (int j=n-1;j>=0;j--) if (f[j]!=inf)
for (int l=1;l<=n-j;l++)
f[j+l]=min(f[j+l],f[j]+a[i]*power(l,b[i]));
}
printf("%lld",f[n]);
return 0;
}
vijos1250 最勇敢的机器人 (并查集预处理+分组背包dp)
#include<cstdio>
#include<algorithm>
#include<string>
#include<cstring>
#include<cmath>
using namespace std;
const int maxn=1022;
int n,w[maxn],p[maxn],wm,k,fa[maxn],hh[maxn],last[maxn],head[maxn],da[maxn],f[maxn];
bool b[maxn]={false};
int find(int x)
{
if (fa[x]==x)return x;
fa[x]=find(fa[x]);
return fa[x];
}
int main()
{
scanf("%d%d%d",&n,&wm,&k);
for (int i=1;i<=n;i++) scanf("%d%d",&p[i],&w[i]);
for (int i=1;i<=n;i++) fa[i]=i;
while (k--)
{
int a,b;
scanf("%d%d",&a,&b);
int af=find(a),bf=find(b);
if (af!=bf) fa[bf]=af;
}
int k;
for (int i=1;i<=n;i++) k=find(i);
memset(head,0,sizeof(head));
for (int i=1;i<=n;i++)
{
last[i]=head[fa[i]];head[fa[i]]=i;
if (hh[fa[i]]==0){hh[fa[i]]=1;da[++da[0]]=fa[i];}
}
for (int i=1;i<=da[0];i++)
{
for (int j=wm;j>=0;j--)
for (int l=head[da[i]];l;l=last[l])
f[j+w[l]]=max(f[j+w[l]],f[j]+p[l]);
}
printf("%d",f[wm]);
return 0;
}
vijos1421多人背包(01背包前k优解)
本题其实是求01背包的前k优解之和,归并一下就好
f[i][j] 表示容量为 i 时第 j 优解的值,注意状态转移时要归并,归并的应用!!!!
(关于题目中所述的“背包必须装满”可以这样处理:把数组全部赋 -INF,特殊的是 f[0][0] = 0,这样一来最后
f[capacity][1..k] 中的负数取值就意味着无法装满。)
这是一种思路,不用我再用另一个bool数组判断容量i时是否有没有,不过另一个数组记录更加直观一些
#include<cmath>
#include<cstring>
#include<string>
#include<algorithm>
#include<cstdio>
using namespace std;
int k,v,n,f[5009][55],s[209],w[209];
int main()
{
scanf("%d%d%d",&k,&v,&n);
for(int i=1;i<=n;i++) scanf("%d%d",&s[i],&w[i]);
memset(f,-0x3f3f3f3f,sizeof(f));
f[0][1]=0;
for (int i=1;i<=n;i++)
{
for (int j=v;j>=s[i];j--)
{
int cnt[55],l=1,r=1;
for (int ll=1;ll<=k;ll++)
if (f[j-s[i]][l]+w[i]>f[j][r]) cnt[ll]=f[j-s[i]][l]+w[i],l++;
else cnt[ll]=f[j][r],r++;//归并,状态转移
for (int ll=1;ll<=k;ll++) f[j][ll]=cnt[ll];
}
}
int ans=0;
for (int i=1;i<=k;i++) ans+=f[v][i];
printf("%d",ans);
return 0;
}
做背包问题最关键的就是找清楚并反问自己?这题里面 什么是容量? 什么是费用? 什么是价值?
容量,就是这题当中我们怎样表示状态的数组。
费用,就是用来f【i】---->f【i+v【k】】,状态转移的跨度
价值,就是你这个dp的数组,所维护的东西。维护的数值!