各种背包的总结
现在才开始写这种彩笔东西qaq
01背包
这个不想多讲了
int n, V, w, v, dp[maxn];
int main()
{
read(n), read(V);
for (int i = 1; i <= n; ++i)
{
read(v), read(w);
for (int j = V; j >= v; --j)
dp[j] = max(dp[j], dp[j - v] + w);
}
printf("%d\n", dp[V]);
return 0;
}
完全背包
这个也没什么好讲的
就是把循环顺序换一下
int n, V, w, v, dp[maxn];
int main()
{
read(n), read(V);
for (int i = 1; i <= n; ++i)
{
read(v), read(w);
for (int j = v; j <= V; ++j)
dp[j] = max(dp[j], dp[j - v] + w);
}
printf("%d\n", dp[V]);
return 0;
}
多重背包
直接暴力
指拆分成一个个,跑01背包
代码就算了
二进制拆分
如标题一样,拆完跑01背包,没什么好说的
int wei[maxn << 2], val[maxn << 2], dp[maxn], cnt;
int main()
{
int n, m, a, b, c;
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; ++i)
{
scanf("%d%d%d", &b, &a, &c);
if (!c)
{
for (int j = b; j <= m; ++j)
dp[j] = max(dp[j], dp[j - b] + a);
continue;
}
for (int j = 1; c >= j; j <<= 1)
{
wei[++cnt] = j * b;
val[cnt] = j * a;
c -= j;
}
if (c)
{
wei[++cnt] = b * c;
val[cnt] = a * c;
}
}
for (int i = 1; i <= cnt; ++i)
for (int j = m; j >= wei[i]; --j)
dp[j] = max(dp[j], dp[j - wei[i]] + val[i]);
printf("%d", dp[m]);
return 0;
}
单调队列优化
首先我们有普通的转移方程
设\(vol\)为体积,\(wei\)为价值,\(num\)为数量
令\(lim=\min(\left \lfloor \frac{V}{vol} \right \rfloor,num)\)
\[dp[i][j]=\max_{0\le k\le lim}{dp[i-1][j-k*vol]+k*wei}
\]
然后我们可以通过对余数进行分组,每组之间互不影响,来用上单调队列求出这个最大值
设我们选了\(k\)个这个物品(\(k\le a=\frac{j}{vol}\)),此时余数为\(b=j\%vol\)
转移方程变为了
\[dp[i][j]=\max_{0\le k\le lim}(dp[i-1][(a-k)*vol+b]+k*wei)
\]
令\(k'=a-k\)
\[dp[i][j]=\max_{a-lim\le k'\le a}(dp[i-1][k'*vol+b]-k'*wei)+a*wei
\]
然后我们就可以上单调队列了
这是模板多重背包的code(顺便说下,前面那几个的模板这个网站上也有)
#include<bits/stdc++.h>
using namespace std;
inline void read(int& x)
{
x = 0; char c = getchar();
while (!isdigit(c)) c = getchar();
while (isdigit(c)) x = x * 10 + c - '0', c = getchar();
}
#define maxn 100005
int dp[maxn], n, v, vol, wei, num, ans, q[maxn], l, r, pos[maxn];
int main()
{
read(n), read(v);
for (int i = 1; i <= n; ++i)
{
read(vol), read(wei), read(num);
if (num > v / vol) num = v / vol;//lim
for (int b = 0; b < vol; ++b)//枚举余数
{
l = 1, r = 0;//初始化单调队列,维护递减的单调队列
for (int t = 0; t <= (v - b) / vol; ++t)//枚举当前的a,单调队列中的是k'
{
int tp = dp[t * vol + b] - t * wei;
while (l <= r && q[r] <= tp) --r;
q[++r] = tp, pos[r] = t;//加进队尾
while (l <= r && t - pos[l] > num) ++l;//排除掉不符合区间的决策
dp[t * vol + b] = max(dp[t * vol + b], q[l] + t * wei);
}
}
}
int ans = 0;
for (int i = 1; i <= v; ++i) ans = max(ans, dp[i]);
printf("%d\n", ans);
return 0;
}
混合背包
模 板 三 合 一
#define maxn 10005
int n, V, w[maxn << 3], v[maxn << 3], s[maxn << 3], dp[maxn], tot;
int q[maxn], pos[maxn];
int main()
{
read(n), read(V);
for (int i = 1, v, w, s; i <= n; ++i)
{
read(v), read(w), read(s);
if (s == -1) s = 1;
if (!s)
for (int j = v; j <= V; ++j)
dp[j] = max(dp[j], dp[j - v] + w);
else
for (int b = 0; b < v; ++b)
for (int l = 1, r = 0, t = 0; t <= (V - b) / v; ++t)
{
int tp = dp[t * v + b] - t * w;
while (l <= r && q[r] <= tp) --r;
q[++r] = tp, pos[r] = t;
while (l <= r && t - pos[l] > s) ++l;
dp[t * v + b] = max(dp[t * v + b], q[l] + t * w);
}
}
printf("%d\n", dp[V]);
return 0;
}
二维费用背包
就是两层01背包循环
#define maxn 1005
int dp[maxn][maxn];
int main()
{
int n,V,M;
read(n),read(V),read(M);
for(int i=1,v,m,w;i<=n;++i)
{
read(v),read(m),read(w);
for(int j=V;j>=v;--j)
for(int k=M;k>=m;--k)
dp[j][k]=max(dp[j][k],dp[j-v][k-m]+w);
}
printf("%d\n",dp[V][M]);
return 0;
}
分组背包
大概就是组内01,各组01
#define mp make_pair
struct Group
{
int num;
vector<pair<int,int> > a;
}g[105];
int f[105];
int main()
{
int n,v;
scanf("%d%d",&n,&v);
int x,y;
for(int i=1;i<=n;++i)
{
scanf("%d",&g[i].num);
for(int j=1;j<=g[i].num;++j)
scanf("%d%d",&x,&y),g[i].a.push_back(mp(x,y));
}
for(int i=1;i<=100;++i)
if(g[i].num)
for(int j=v;j;--j)//注意两个循环的顺序,这样保证了同一组最多选一个
for(vector<pair<int,int> >::iterator it=g[i].a.begin();it!=g[i].a.end();++it)
if((*it).first<=j)
f[j]=max(f[j],f[j-(*it).first]+(*it).second);
printf("%d\n",f[v]);
return 0;
}
有依赖的背包问题
这其实有点像树形DP了
其实就是树形DP
结构会形成一颗树(假设没有环)
#define maxn 105
struct Edge
{
int fr,to;
}eg[maxn<<1];
int head[maxn],edgenum,n,v,root,val[maxn],w[maxn];
int dp[maxn][maxn];
inline void add(int fr,int to)
{
eg[++edgenum]={head[fr],to};
head[fr]=edgenum;
}
void dfs(int rt,int fa)
{
for(int i=head[rt];i;i=eg[i].fr)
{
if(eg[i].to==fa) continue;
dfs(eg[i].to,rt);
for(int j=v-w[rt];j>=0;--j)
for(int k=0;k<=j;++k)
dp[rt][j]=max(dp[rt][j],dp[rt][j-k]+dp[eg[i].to][k]);
}
for(int i=v;i>=w[rt];--i)//必选当前这个点
dp[rt][i]=dp[rt][i-w[rt]]+val[rt];
for(int i=0;i<w[rt];++i)
dp[rt][i]=0;
}
int main()
{
scanf("%d%d",&n,&v);
int p;
for(int i=1;i<=n;++i)
{
scanf("%d%d%d",&w[i],&val[i],&p);//w是体积,val是价值
if(p==-1) p=0,root=i;
add(i,p),add(p,i);
}
dfs(root,0);
printf("%d\n",dp[root][v]);
return 0;
}
一切伟大的行动和思想,都有一个微不足道的开始。
There is a negligible beginning in all great action and thought.
There is a negligible beginning in all great action and thought.