背包真假小记

前言

传统意义上的背包是有一些众所周知的定义和判断性质的
\(n\)件物品,每件物品有价值、体积、数量(1个、无限、规定数量)等属性(一般就是这三个),彼此可能存在依赖或排斥,现在要把它们尽量放入一个容积为\(m\)的背包里,问获得的最大价值
然后主要分为01背包、完全背包、多重背包等(比如还有混合背包(前三个总和)、二维费用背包、分组背包等)

01背包:时间复杂度\(O(nm)\),空间复杂度\(O(m)\)

完全背包:时间复杂度\(O(nm)\),空间复杂度\(O(m)\)

多重背包:时间复杂度\(O(nm\sum cnt)\),空间复杂度\(O(m)\),二进制优化时间复杂度变成\(O(nm\log \sum cnt)\)

多重背包单调队列优化:时间复杂度\(O(nm)\),空间复杂度\(O(m)\)

不管怎样,背包问题始终无法脱离时间复杂度\(O(nm)\),空间复杂度\(O(m)\)的框框吧(至少我现在是这么认为的)

那么对于\(n,m\)超过\(10000\)很有可能并不是背包。

而且注意背包问题是不能够贪心的,除了部分背包(可以塞一部分当然按性价比排序,它也不属于背包)

然后一旦题目的模型可以转换成背包,可以暗暗庆幸——又是道思维题(然而我连模型转换都不会,我太菜了)


CF3B Lorry

题目

\(n(1\leq n\leq 10^5)\)件物品,每件物品有自身的价值(\(\leq 10^4\))和体积(1或2),背包大小为\(m(1\leq m\leq 10^9)\),问可获得的最大价值,并输出其中一种方案(所选每件物品的编号)


分析

背包真假鉴定:假(时空都不允许)

考虑给的限制,体积为1或2,那只能是贪心,但是怎么贪,

首先肯定选价值大的,有体积的影响就分别降序排序

如果选了若干个体积为1的,那么肯定剩下全部用体积为2的填(反过来同理)

所以可以排序后将体积为2的弄成前缀和,那么我选了价值最大的一部分体积为1的物品,再用\(\text{if}\)判断填完体积2的答案会不会更好,再标记选择的编号

所以就是一道妥妥的贪心题


代码

#include <cstdio>
#include <cctype>
#include <algorithm>
#define rr register
using namespace std;
struct rec{
	int w,rk;
	bool operator <(const rec &t)const{
	     return w>t.w;
	}
}c1[100011],c2[100011];
int n,m,n1,n2,s[1000011],ans,p1,p2;
inline signed iut(){
	rr int ans=0; rr char c=getchar();
	while (!isdigit(c)) c=getchar();
	while (isdigit(c)) ans=(ans<<3)+(ans<<1)+(c^48),c=getchar();
	return ans; 
}
inline void print(int ans){
	if (ans>9) print(ans/10);
	putchar(ans%10+48);
}
signed main(){
	n=iut(),m=iut();
	for (rr int i=1;i<=n;++i){
		rr int x=iut(),y=iut();
		if (x==1) c1[++n1]=(rec){y,i};
		    else c2[++n2]=(rec){y,i};
	}
	sort(c1+1,c1+1+n1),sort(c2+1,c2+1+n2);
	for (rr int i=1;i<=n2;++i) s[i]=s[i-1]+c2[i].w;
	for (rr int i=0,sum=0;i<=n1;++i){
		rr int now=(m-i)>>1; sum+=c1[i].w;
		if (now<0) break; if (now>n2) now=n2;
		if (sum+s[now]>ans) ans=sum+s[now],p1=i,p2=now;
	}
	print(ans),putchar(10);
	for (rr int i=1;i<=p1;++i) print(c1[i].rk),putchar(32);
	for (rr int i=1;i<=p2;++i) print(c2[i].rk),putchar(32);
	return 0;
} 

比赛G题

题目

\(n(1\leq n\leq 2*10^6)\)件物品,每件物品有自身的价值(1或2)和体积(\(\leq 10^9\)),背包大小为\(m\),问可获得的最大价值


分析

上一题已经揭示这题应该差不多,然而数据被部分背包水过去了
考虑二分答案,把体积和价值反过来,判断最小价值(原体积)是否不超过背包大小
和上一题有些不同的是,这题二分的背包大小(不是原来的\(m\))可以变大,
换一句话说背包必须得塞满,具体细节可以思考一下


代码

#include <cstdio>
#include <cctype>
#include <algorithm>
#define rr register
using namespace std;
int n,c1[2000011],c2[2000011],n1,n2,l,r; long long wm,s[2000011];
inline signed iut(){
    rr int ans=0; rr char c=getchar();
    while (!isdigit(c)) c=getchar();
    while (isdigit(c)) ans=(ans<<3)+(ans<<1)+(c^48),c=getchar();
    return ans; 
}
inline void print(int ans){
    if (ans>9) print(ans/10);
    putchar(ans%10+48);
}
inline bool check(int m){
	rr long long ans=wm+1,sum=0;
	for (rr int i=0;i<=n1;++i){
		rr int now=(m-i+1)/2; sum+=c1[i];
		if (now<0) now=0; if (now>n2) continue;//必须塞满,塞不满不能改变答案
		if (sum+s[now]<ans) ans=sum+s[now];
	}
	return ans<wm+1;
}
signed main(){
    n=iut(),scanf("%lld",&wm);
    for (rr int i=1;i<=n;++i){
    	rr int w=iut(),c=iut();
    	if (c==1) c1[++n1]=w;
    	    else c2[++n2]=w;
    	r+=c;
	}
	for (rr int i=1;i<=n2;++i) s[i]=s[i-1]+c2[i];
	while (l<r){
		rr int mid=(l+r+1)>>1;
		if (check(mid)) l=mid;
		    else r=mid-1;
	}
    return !printf("%d",l);
}

JZOJ 5184 Gift

题目

\(n(\leq 1000)\)个物品,背包容量大小为\(m(\leq 1000)\)
问有多少种方案使背包尽量被填充(也就是填不了任何一件未填入的物品)


分析

这题数据范围就是个背包,不由得想到用背包求方案数来做
但是怎么统计呢,首先降序排序再求后缀和
那么所能选择的部分只能是\(\sum_{j=m-a[i]+1}^{m-a[i+1]}f[j-s[i+1]]\)
这部分要填完\(a[i+1\sim n]\),剩下的就是可选方案(最小的是\(a[i]\))


代码

#include <cstdio>
#include <cctype>
#include <algorithm>
#define rr register
#define mod 10000007
using namespace std;
int n,m,a[1001],s[1001],f[1001],ans;
inline signed iut(){
	rr int ans=0; rr char c=getchar();
	while (!isdigit(c)) c=getchar();
	while (isdigit(c)) ans=(ans<<3)+(ans<<1)+c-48,c=getchar();
	return ans;
}
signed main(){
	n=iut(); m=iut(); f[0]=1;
	for (rr int i=1;i<=n;++i) a[i]=iut();
	sort(a+1,a+1+n); reverse(a+1,a+1+n);
	for (rr int i=n-1;i;--i) s[i]=s[i+1]+a[i];
	if (s[1]<=m) return !printf("1");
	for (rr int i=1;i<=n;++i){
		for (rr int j=m;j>=a[i];--j) f[j]=(f[j]+f[j-a[i]])%mod;
		for (rr int j=m-a[i]+1;j<=m-a[i+1];++j)
		if (j>=s[i+1]) ans=(ans+f[j-s[i+1]])%mod;
	}
	printf("%d",ans);
	return 0;
}

JZOJ 4224 食物

题目

\(n\)种食物,有美味度、大小、份数三种属性,还有\(m\)种工具,有费用、单个运载大小、个数三种属性(六种属性均在100以内),\(n,m\leq 200,p\leq 50000\)
要用工具装这些食物,可以将一份食物拆开装,但是必须装完这份食物才算美味度,问是否能在运输费用\(\leq 50000\)的情况下满足美味度\(\geq p\)


分析

首先我只要满足美味度就好了,然后肯定想总食物大小越小越省钱,所以可以转过来,设\(f[j]\)表示美味度为\(j\)的最小空间
然后设\(dp[j]\)表示花费\(j\)可以换到的最大空间,然后判断就好了


代码

#include <cstdio>
#include <cctype>
#include <algorithm>
#define rr register
using namespace std;
const int M=50000; int f[M+105],dp[M+5];
struct rec{int w,c;}a[1201],b[1201];
inline signed iut(){
	rr int ans=0; rr char c=getchar();
	while (!isdigit(c)) c=getchar();
	while (isdigit(c)) ans=(ans<<3)+(ans<<1)+(c^48),c=getchar();
	return ans;
}
inline void min(int &a,int b){if (a>b) a=b;}
inline void max(int &a,int b){if (a<b) a=b;}
signed main(){
	for (rr int t=iut();t;--t){
		rr int n1=iut(),n2=iut(),p=iut(),i1,poi=M+1,ans=M+1;
		for (i1=n1,n1=0;i1;--i1){
		    rr int w=iut(),c=iut(),t=iut();
		    for (rr int j=1;t>=j;t-=j,j<<=1)
		    	a[++n1]=(rec){w*j,c*j};
		    if (t) a[++n1]=(rec){w*t,c*t};
		}
		for (i1=n2,n2=0;i1;--i1){
		    rr int c=iut(),w=iut(),t=iut();
		    for (rr int j=1;t>=j;t-=j,j<<=1)
		    	b[++n2]=(rec){w*j,c*j};
		    if (t) b[++n2]=(rec){w*t,c*t};
		}
		fill(f+1,f+M+101,100001); f[0]=0;
		for (rr int i=1;i<=n1;++i)
		for (rr int j=M+100;j>=a[i].w;--j){
		    min(f[j],f[j-a[i].w]+a[i].c);
		    if (j>=p) min(poi,f[j]);
		}
		fill(dp+1,dp+M+1,-100001); dp[0]=0;
		for (rr int i=1;i<=n2;++i)
		for (rr int j=ans;j>=b[i].w;--j){
		    max(dp[j],dp[j-b[i].w]+b[i].c);
		    if (dp[j]>=poi) min(ans,j);
		}
	    if (ans>M) printf("TAT\n"); else printf("%d\n",ans);
	}
	return 0;
} 

JZOJ 6309 完全背包

题目

完全背包模板改动版,\(n\leq 10^6,m\leq 10^{16},w,c\leq 10^2\)


分析

肯定不是裸背包,考虑\(w,c\)突破口,那可以说明\(n\)是唬人的,实际上最多有100件物品(完全背包)
然后如果所有物品都选1次,那么大小最多10000,所以肯定存在一个类似于循环节的存在,
所以背包可以最多跑10000,然后枚举背包循环节判断最大值


代码

#include <cstdio>
#include <cctype>
#include <algorithm>
#define rr register
#define max(a,b) ((a)>(b)?(a):(b))
using namespace std;
typedef long long ll;
int f[10001],n,a[101]; ll ans,m;
inline signed iut(){
	rr int ans=0; rr char c=getchar();
	while (!isdigit(c)) c=getchar();
	while (isdigit(c)) ans=(ans<<3)+(ans<<1)+(c^48),c=getchar();
	return ans;
}
signed main(){
	freopen("backpack.in","r",stdin);
	freopen("backpack.out","w",stdout);
	n=iut(); scanf("%lld",&m);
	for (rr int i=1,x,y;i<=n;++i)
	    x=iut(),y=iut(),a[x]=max(a[x],y);
	for (rr int i=1;i<=100;++i) if (a[i])
	for (rr int j=i;j<=10000;++j)
	    f[j]=max(f[j],f[j-i]+a[i]);
	for (rr int i=1;i<=10000;++i)
	    ans=max(ans,m/i*f[i]+f[m%i]);
	return !printf("%lld",ans);
}

后续

wtcl,如果还有就以后单独写吧(然后再放超链接233)

posted @ 2020-01-29 21:40  lemondinosaur  阅读(329)  评论(1)    收藏  举报