3.17~3.28背包问题

#01背包#
题库P5#416采药
i循环为阶段,即当前考虑第i个物品取不取
j循环为状态,即当前背包容量还剩j
**f[i][j]**表示已经考虑了前i个物品,背包已装了容量为j的物品时的最大价值
Q:这里j为什么是倒序循环?
A:用反证法——如果j正序循环,那么当更新f[j]时会发现f[j-t[i]]可能已经被更新过。而如果f[j-t[i]]已被更新过的话说明第i个物品已经取了1次,此时再要更新f[j]的话会导致第i个物品又取了1次,这就与01背包每个物品只取1次相违背。因此需要倒序循环。

#include<bits/stdc++.h>
using namespace std;
long long T,m,t[110],v[110],f[1010];
inline int read()
{
	int num=0,flag=1;
	char c=getchar();
	for (;c<'0'||c>'9';c=getchar())
	if (c=='-') flag=-1;
	for (;c>='0'&&c<='9';c=getchar())
	num=(num<<3)+(num<<1)+c-48;
	return num*flag;
}
int main()
{
	T=read();
	m=read();
	for (int i=1;i<=m;++i)
	{
		t[i]=read();
		v[i]=read();
	}
	for (int i=1;i<=m;++i)
	for (int j=T;j>=t[i];--j)
	f[j]=max(f[j],f[j-t[i]]+v[i]);
	printf("%lld\n",f[T]);
	return 0;
}

#完全背包#
题库P5#418完全背包模版
同样地,i为阶段,j为状态,**f[i][j]**的意义也一样
但这里,j是正序循环。这是因为一个物品可以取多次,f[j]可以在f[j-t[i]]被更新的基础上再次被更新,这样就保证了第i个物品可以取多次。

#include<bits/stdc++.h>
using namespace std;
long long m,n,v[110],w[110],f[310];
inline int read()
{
	int num=0,flag=1;
	char c=getchar();
	for (;c<'0'||c>'9';c=getchar())
	if (c=='-') flag=-1;
	for (;c>='0'&&c<='9';c=getchar())
	num=(num<<3)+(num<<1)+c-48;
	return num*flag;
}
int main()
{
	m=read();
	n=read();
	for (int i=1;i<=n;++i)
	{
		w[i]=read();
		v[i]=read();
	}
	for (int i=1;i<=n;++i)
	for (int j=w[i];j<=m;++j)
	f[j]=max(f[j],f[j-w[i]]+v[i]);
	printf("max=%lld\n",f[m]);
	return 0;
}

#多重背包#
题库P5#420逃亡的准备
多重背包最终目的是把它转换成01背包问题。那它是怎么做到的呢?
这里采用二进制拆分法——一个物品有mm个,那么把mm拆成 2 0 + 2 1 + 2 2 + … + m m ′ 2^0+2^1+2^2+…+mm' 20+21+22++mm,mm’值为 m m − 2 0 − 2 1 − 2 2 − … mm-2^0-2^1-2^2-… mm202122,减到不能再减为止,剩下的值就是它(不为负数)。那么我们就可以用这里的数凑成所有<=mm的数。因此这就转换成了01背包问题。

#include<bits/stdc++.h>
using namespace std;
long long n,v,mm,ww,ss,w[10000010],s[10000010],tot,f[5010];
inline int read()
{
	int num=0,flag=1;
	char c=getchar();
	for (;c<'0'||c>'9';c=getchar())
	if (c=='-') flag=-1;
	for (;c>='0'&&c<='9';c=getchar())
	num=(num<<3)+(num<<1)+c-48;
	return num*flag;
}
int main()
{
	n=read();
	v=read();
	for (int i=1;i<=n;++i)
	{
		mm=read();
		ww=read();
		ss=read();
		int j=1;
		while (1)
		{
			if (mm>j)
			{
				tot++;
				mm-=j;
				w[tot]=ww*j;
				s[tot]=ss*j;
			}
			else
			{
				tot++;
				w[tot]=ww*mm;
				s[tot]=ss*mm;
				break;
			}
			j*=2;
		}
	}
	for (int i=1;i<=tot;++i)
	for (int j=v;j>=w[i];--j)
	f[j]=max(f[j],f[j-w[i]]+s[i]);
	printf("%lld\n",f[v]);
	return 0;
}

#混合三种背包#
暂时没有题目
友情链接:https://blog.csdn.net/qq_39670434/article/details/79475683
#二维费用的背包#
暂时没有题目
友情链接:https://blog.csdn.net/qq_39670434/article/details/79476427
#分组背包#
题库P5#422竞赛真理
因为每一组的物品间有冲突,每一组里最多只能取一个物品,那么在每一组里面采取01背包的思想,在每一组里考虑该物品取不取。

#include<bits/stdc++.h>
using namespace std;
long long n,t,w1[50],w2[50],t1[50],t2[50],f[1080010];
inline int read()
{
	int num=0,flag=1;
	char c=getchar();
	for (;c<'0'||c>'9';c=getchar())
	if (c=='-') flag=-1;
	for (;c>='0'&&c<='9';c=getchar())
	num=(num<<3)+(num<<1)+c-48;
	return num*flag;
}
int main()
{
	n=read();
	t=read();
	for (int i=1;i<=n;++i)
	{
		w1[i]=read();
		t1[i]=read();
		w2[i]=read();
		t2[i]=read();
	}
	for (int i=1;i<=n;++i)
	for (int j=t;j>=min(t1[i],t2[i]);--j)
	{
		if (j>=t1[i]) f[j]=max(f[j],f[j-t1[i]]+w1[i]);
		if (j>=t2[i]) f[j]=max(f[j],f[j-t2[i]]+w2[i]);
	}
	printf("%lld\n",f[t]);
	return 0;
}

8.21华为云1003CDN流量调度问题
每一组里恰好只能取一个物品

#include<bits/stdc++.h>
using namespace std;
int T,n,m,a[110],t[110],c[110],w[110][10010],s[110][10010],f[10010],ans;
inline int read()
{
	int num=0,flag=1;
	char c=getchar();
	for (;c<'0'||c>'9';c=getchar())
	if (c=='-') flag=-1;
	for (;c>='0'&&c<='9';c=getchar())
	num=(num<<3)+(num<<1)+c-48;
	return num*flag;
}
int main()
{
	//freopen(".in","r",stdin);
	//freopen(".out","w",stdout);
	T=read();
	while (T--)
	{
		n=read();
		m=read();
		for (int i=1;i<=n;++i)
		a[i]=read();
		for (int i=1;i<=n;++i)
		t[i]=read();
		memset(c,0,sizeof(c));
		for (int i=1;i<=n;++i)
		for (int j=0;j<=m;++j)
		{
			if (j+1>t[i]) break;
			if (j&&((a[i]-1)/(j+1)+1)==s[i][c[i]]) continue;
			++c[i];
			w[i][c[i]]=j;
			s[i][c[i]]=((a[i]-1)/(j+1)+1);
		}
		memset(f,0x3f,sizeof(f));
		f[0]=0;
		for (int i=1;i<=n;++i)
		for (int j=m;j>=0;--j)
		{
			f[j]=f[j-w[i][1]]+s[i][1];
			for (int k=2;k<=c[i];++k)
			if (j-w[i][k]>=0) f[j]=min(f[j],f[j-w[i][k]]+s[i][k]);
		}
		ans=0x3f3f3f3f;
		for (int j=m;j>0;--j)
		ans=min(ans,f[j]);
    	printf("%d\n",ans);
    }
	fclose(stdin);
	fclose(stdout);
	return 0;
}

#有依赖的背包问题#
题库P5#423金明的预算方案
有依赖的背包是加强版的分组背包问题,只不过中间加了一个特判,一组物品中可能可以取多个而已。因为是取了主件才可以取附件,因此一定要加这个if判断。而可以省略else的原因是——在if语句里的第一句话已经取了一个max,如果不取主件,那么f[j]还是f[j];如果要取主件,f[j]是会被更新的。

#include<bits/stdc++.h>
using namespace std;
long long n,m,vv,pp,qq,v[100][5],p[100][5],f[32010];
inline int read()
{
	int num=0,flag=1;
	char c=getchar();
	for (;c<'0'||c>'9';c=getchar())
	if (c=='-') flag=-1;
	for (;c>='0'&&c<='9';c=getchar())
	num=(num<<3)+(num<<1)+c-48;
	return num*flag;
}
int main()
{
	n=read();
	m=read();
	for (int i=1;i<=m;++i)
	{
		vv=read();
		pp=read();
		qq=read();
		if (qq==0)
		{
			v[i][0]=vv;
			p[i][0]=pp;
		}
		else
		{
			if (v[qq][1]==0)
			{
				v[qq][1]=vv;
				p[qq][1]=pp;
			}
			else
			{
				v[qq][2]=vv;
				p[qq][2]=pp;
			}
		}
	}
	for (int i=1;i<=m;++i)
	for (int j=n;j>=0;--j)
	if (j>=v[i][0])
	{
		f[j]=max(f[j],f[j-v[i][0]]+v[i][0]*p[i][0]);
		if (j>=v[i][0]+v[i][1])
		f[j]=max(f[j],f[j-v[i][0]-v[i][1]]+v[i][0]*p[i][0]+v[i][1]*p[i][1]);
		if (j>=v[i][0]+v[i][2])
		f[j]=max(f[j],f[j-v[i][0]-v[i][2]]+v[i][0]*p[i][0]+v[i][2]*p[i][2]);
		if (j>=v[i][0]+v[i][1]+v[i][2])
		f[j]=max(f[j],f[j-v[i][0]-v[i][1]-v[i][2]]+v[i][0]*p[i][0]+v[i][1]*p[i][1]+v[i][2]*p[i][2]);
	}
	printf("%lld\n",f[n]);
	return 0;
}

#泛化物品背包#
暂时没有题目
友情链接:https://blog.csdn.net/qq_39670434/article/details/79483102
#背包问题的变化#
呵,变化可真多!
##T1##
题库P5#417集合求和
这是01背包的求方案总数,把max改成求和

#include<bits/stdc++.h>
using namespace std;
long long n,sum,a[50],f[1000010]={1}; 
inline int read()
{
	int num=0,flag=1;
	char c=getchar();
	for (;c<'0'||c>'9';c=getchar())
	if (c=='-') flag=-1;
	for (;c>='0'&&c<='9';c=getchar())
	num=(num<<3)+(num<<1)+c-48;
	return num*flag;
}
int main()
{
	n=read();
	sum=(n+1)*n/2;
	if (sum%2==1)
	{
		printf("0\n");
		return 0;
	}
	for (int i=1;i<=n;++i)
	a[i]=i;
	for (int i=1;i<=n;++i)
	for (int j=sum/2;j>=a[i];--j)
	f[j]=f[j]+f[j-a[i]];
	printf("%lld\n",f[sum/2]/2);
	return 0;
}

##T2##
题库P5#419质数和分解
这是完全背包的求方案总数,同样地,求和

#include<bits/stdc++.h>
using namespace std;
long long n,a[5010],temp,f[1000010]={1}; 
inline int read()
{
	int num=0,flag=1;
	char c=getchar();
	for (;c<'0'||c>'9';c=getchar())
	if (c=='-') flag=-1;
	for (;c>='0'&&c<='9';c=getchar())
	num=(num<<3)+(num<<1)+c-48;
	return num*flag;
}
bool panduan(int t)
{
	for (int i=2;i<=sqrt(t);++i)
	if (t%i==0) return false;
	return true;
}
int main()
{
	n=read();
	for (int i=2;i<=n;++i)
	if (panduan(i)) a[++temp]=i;
	for (int i=1;i<=temp;++i)
	for (int j=a[i];j<=n;++j)
	f[j]=f[j]+f[j-a[i]];
	printf("%lld\n",f[n]);
	return 0;
}

##T3##
题库P5#421潜水员的规划
因为它要计算为了完成他的工作需要的气缸的重量的最低值,首先要保证能完成他的工作,这就要求**f[][]**的下标>=T和A,所以i和j循环枚举的时候要适当扩大范围,然后把max改成min

#include<bits/stdc++.h>
using namespace std;
long long T,A,n,t[1010],a[1010],w[1010],f[1000][1000],ans;
inline int read()
{
	int num=0,flag=1;
	char c=getchar();
	for (;c<'0'||c>'9';c=getchar())
	if (c=='-') flag=-1;
	for (;c>='0'&&c<='9';c=getchar())
	num=(num<<3)+(num<<1)+c-48;
	return num*flag;
}
int main()
{
	T=read();
	A=read();
	n=read();
	for (int i=1;i<=n;++i)
	{
		t[i]=read();
		a[i]=read();
		w[i]=read();
	}
	memset(f,10,sizeof(f));
	ans=f[0][0];
	f[0][0]=0;
	for (int i=1;i<=n;++i)
	for (int j=T+100;j>=t[i];--j)
	for (int k=A+100;k>=a[i];--k)
	f[j][k]=min(f[j][k],f[j-t[i]][k-a[i]]+w[i]);
	for (int i=T;i<=T+100;++i)
	for (int j=A;j<=A+100;++j)
	ans=min(ans,f[i][j]);
	printf("%lld\n",ans);
	return 0;
}

##T4##
题库P5#425背包的第k优解
这是求前k优解之和,大佬采用的都是用两个标记外加一个数组,逐层更新。这里**f[j][k]**表示的就是在背包体积为j时的第k优解的价值。

#include<bits/stdc++.h>
using namespace std;
long long K,V,n,t[210],v[210],a1,a2,temp,a[100],f[5010][100],ans; 
inline int read()
{
	int num=0,flag=1;
	char c=getchar();
	for (;c<'0'||c>'9';c=getchar())
	if (c=='-') flag=-1;
	for (;c>='0'&&c<='9';c=getchar())
	num=(num<<3)+(num<<1)+c-48;
	return num*flag;
}
int main()
{
	K=read();
	V=read();
	n=read();
	for (int i=1;i<=n;++i)
	{
		t[i]=read();
		v[i]=read(); 
	}
	memset(f,128,sizeof(f));
	f[0][1]=0;
	for (int i=1;i<=n;++i)
	for (int j=V;j>=t[i];--j)
	{
		a1=1;
		a2=1;
		temp=0;
		while (temp<K)
		{
			if (f[j][a1]>=f[j-t[i]][a2]+v[i])
			{
				a[++temp]=f[j][a1];
				a1++;
			}
			else
			{
				a[++temp]=f[j-t[i]][a2]+v[i];
				a2++;
			}
		}
		for (int k=1;k<=K;++k)
		f[j][k]=a[k];
	}
	for (int i=1;i<=K;++i)
	ans+=f[V][i];
	printf("%lld\n",ans);
	return 0;
}

##坑##
破锣乐队(还没做过

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值