123789456ye

已AFO

各种背包的总结

现在才开始写这种彩笔东西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;
}
posted @ 2020-04-04 20:32  123789456ye  阅读(164)  评论(0编辑  收藏  举报