背包真假小记
前言
传统意义上的背包是有一些众所周知的定义和判断性质的
有\(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)

浙公网安备 33010602011771号