背包小专题
- 1. CF106C Buns
- 题目描述
- 题目概况
- 思路点拨
- 代码实现
- 2. CF864E Fire
- 题目描述
- 题目概况
- 思路点拨
- 代码实现
- 3. CF366C Dima and Salad
- 题目描述
- 题目概况
- 思路点拨
- 背包瓶颈
- 解决方法
- 代码实现
- 4. CF1132E Knapsack
- 题目描述
- 题目概况
- 思路点拨
- 代码实现
- 5. CF632E Thief in a Shop
- 题目描述
- 题目概况
- 思路分析
- 基本分析
- 单一性反推多样性
- 思想迸射
- 思路一般化处理
- 代码实现
1. CF106C Buns
题目描述
https://www.luogu.com.cn/problem/CF106C
题目概况
来源:Codeforces
洛谷难度: 黄题 \color{yellow}黄题 黄题
CF难度: 1700 1700 1700
标签: 01 背包 01背包 01背包
思路点拨
对于包子,包子的数量有上限 ⌊ a i b i ⌋ \lfloor \frac{a_i}{b_i}\rfloor ⌊biai⌋ 即特殊标记馅料数量比上一个 i i i 包子所需要的馅料数量。
一种包子的上限最多是 100 100 100, 面包的上限是 1000 1000 1000, 最多有 10 10 10 种包子,所以物品的上限是 2000 2000 2000, 01 背包即可 01背包即可 01背包即可
时间复杂度 : O ( 2000 ⋅ n ) ≈ 2 e 6 \Omicron(2000\cdot n)\approx 2e6 O(2000⋅n)≈2e6
AC.
代码实现
#include<bits/stdc++.h>
using namespace std;const int maxn=50;
const int maxm=1e3+10;
int n,m,x,y,c,d,cnt;
int f[maxm][maxm];
struct node{int w,v;
}a[maxn*maxm];
inline void calculate(int num,int w,int v){int s=1;while(num){if(num>=s){a[++cnt].w=s*w;a[cnt].v=s*v;num-=s;}else {a[++cnt].w=num*w;a[cnt].v=num*v;num=0;}s*=2;}return ;
}
int main(){scanf("%d%d%d%d",&m,&n,&c,&d);calculate(1000,c,d);for(int i=1;i<=n;i++){scanf("%d%d%d%d",&x,&y,&c,&d);calculate(x/y,c,d);}
// printf("%d\n",cnt);for(int i=1;i<=cnt;i++){for(int j=0;j<=m;j++){f[i][j]=f[i-1][j];if(j>=a[i].w)f[i][j]=max(f[i][j],f[i-1][j-a[i].w]+a[i].v);}}printf("%d\n",f[cnt][m]);return 0;
}
2. CF864E Fire
题目描述
https://www.luogu.com.cn/problem/CF864E
题目概况
来源:Codeforces
洛谷难度: 绿题 \color{green}绿题 绿题
CF难度: 2000 2000 2000
标签: 01 背包 01背包 01背包
思路点拨
对于 ∀ i \forall i ∀i 都有时间上限为 d i − 1 d_{i}-1 di−1, 01 背包 01背包 01背包 记录路径即可
时间复杂度 : O ( max d i ⋅ n ) ≈ 2 e 5 \Omicron(\max{d_{i}}\cdot n)\approx 2e5 O(maxdi⋅n)≈2e5
AC.
代码实现
#include<bits/stdc++.h>
using namespace std;const int maxn=100+10;
const int maxm=2e3+10;
int n,tot;
int ans[maxn];
int f[maxn][maxm];
int fa[maxn][maxm][3];
struct item{int t,d,p,op;inline void input(){scanf("%d%d%d",&t,&d,&p);}
}a[maxn];
inline bool cmp(item nx,item ny){return nx.d<ny.d;}
inline void calculate(int x,int y,int z){if(x==0){if(z==1)ans[++tot]=a[x+1].op;return ;}if(z==1)ans[++tot]=a[x+1].op;calculate(fa[x][y][0],fa[x][y][1],fa[x][y][2]);return ;
}
int main(){scanf("%d",&n);for(int i=1;i<=n;i++){a[i].input();a[i].op=i;}sort(a+1,a+n+1,cmp);for(int i=1;i<=n;i++){for(int j=0;j<a[i].d;j++){f[i][j]=f[i-1][j];fa[i][j][0]=i-1;fa[i][j][1]=j;fa[i][j][2]=0;if(j>=a[i].t){if(f[i][j]<f[i-1][j-a[i].t]+a[i].p){f[i][j]=f[i-1][j-a[i].t]+a[i].p;fa[i][j][0]=i-1;fa[i][j][1]=j-a[i].t;fa[i][j][2]=1; }} }}int s=0;for(int i=1;i<a[n].d;i++){if(f[n][i]>f[n][s])s=i;}printf("%d\n",f[n][s]);calculate(fa[n][s][0],fa[n][s][1],fa[n][s][2]);printf("%d\n",tot);reverse(ans+1,ans+tot+1);for(int i=1;i<=tot;i++)printf("%d ",ans[i]); return 0;
}
3. CF366C Dima and Salad
题目描述
https://www.luogu.com.cn/problem/CF366C
题目概况
来源:Codeforces
洛谷难度: 蓝题 \color{blue}蓝题 蓝题
CF难度: 1900 1900 1900
标签: 01 背包 01背包 01背包
思路点拨
背包瓶颈
因为要使 ∑ a [ i ] = k ⋅ ∑ b [ i ] \sum a[i]=k \cdot \sum b[i] ∑a[i]=k⋅∑b[i] 这个条件看起来十分的恶心。
解决方法
令背包容量 p = a i − k ⋅ b i p=a_{i}-k\cdot b_{i} p=ai−k⋅bi
若 p < 0 p<0 p<0 时扔入背包 f f f 的序列
若 p > = 0 p>=0 p>=0 时扔入背包 g g g 的序列
f [ i ] 、 g [ i ] f[i]、g[i] f[i]、g[i] 均表示背包容量等于 i i i 的最大美味值
最终答案即为 max ( f i + g i ) \max{(f_{i}+g_{i})} max(fi+gi) ,如果 ∀ f i 或 g i \forall f_{i} 或 g_{i} ∀fi或gi 都不成立,则答案为 − 1 -1 −1
时间复杂度 : O ( max a [ i ] − k ⋅ b [ i ] ⋅ n 2 ) ≈ 1 e 6 \Omicron(\max{a[i]-k\cdot b[i]}\cdot n^2)\approx 1e6 O(maxa[i]−k⋅b[i]⋅n2)≈1e6
AC.
代码实现
#include<bits/stdc++.h>
#define int long long
using namespace std;const int maxn=100+10;
const int maxm=1e5+10;
const int inf=1e18;
int n,k,cnt1,cnt2;
int f[maxm],g[maxm],t[maxn];
struct node{int w,v;
}a[maxn],b[maxn];
signed main(){scanf("%lld%lld",&n,&k);for(int i=1;i<=n;i++)scanf("%lld",&t[i]);for(int i=1;i<=n;i++){int u;scanf("%lld",&u);int p=t[i]-k*u;if(p<=0){a[++cnt1].w=abs(p);a[cnt1].v=t[i];}else {b[++cnt2].w=p;b[cnt2].v=t[i];}}for(int i=1;i<=100000;i++)f[i]=g[i]=-inf;for(int i=1;i<=cnt1;i++){for(int j=100000;j>=a[i].w;j--)f[j]=max(f[j],f[j-a[i].w]+a[i].v);}for(int i=1;i<=cnt2;i++){for(int j=100000;j>=b[i].w;j--)g[j]=max(g[j],g[j-b[i].w]+b[i].v);}int ans=0;for(int i=0;i<=100000;i++)ans=max(ans,f[i]+g[i]);if(ans==0)ans=-1;printf("%lld\n",ans);return 0;
}
4. CF1132E Knapsack
题目描述
https://www.luogu.com.cn/problem/CF1132E
题目概况
来源:Codeforces
洛谷难度: 蓝题 \color{blue}蓝题 蓝题
CF难度: 2300 2300 2300
标签: 01 背包 01背包 01背包
思路点拨
将单个物品打成 840 840 840 的堆
若 W ≥ ∑ 1 8 c n t i ⋅ i W \geq \sum_1^8 cnt_{i}\cdot i W≥∑18cnti⋅i 则答案为 ∑ 1 8 c n t i ⋅ i \sum_1^8 cnt_{i}\cdot i ∑18cnti⋅i
否则 840 840 840 堆用完和未用完,剩余的数量不足 840 840 840,直接01
时间复杂度 : O ( 1000 ∗ 840 ) ≈ 1 e 6 \Omicron(1000*840)\approx 1e6 O(1000∗840)≈1e6
AC.
代码实现
#include<bits/stdc++.h>
#define int long long
using namespace std;const int maxn=20+10;
const int maxm=1e4+10;
const int maxk=1e3+10;
int w,s,cnt,tot;
int a[maxn],b[maxn],v[maxm],f[maxk];
signed main(){scanf("%lld",&w);s=w/840;for(int i=1;i<=8;i++){scanf("%lld",&a[i]);b[i]=a[i]/(840/i);a[i]=a[i]%(840/i);if(s>=b[i]){s-=b[i];b[i]=0;}else {b[i]-=s;s=0;}}for(int i=1;i<=8;i++){if(b[i]>0)a[i]=840/i;tot=tot+i*a[i];for(int j=1;j<=a[i];j++)v[++cnt]=i;}int p=w%840;if(s==0){for(int i=1;i<=cnt;i++){for(int j=p;j>=v[i];j--)f[j]=max(f[j],f[j-v[i]]+v[i]);}printf("%lld\n",w-(p-f[p]));}else {p=p+s*840;if(p>=tot)printf("%lld\n",w-(p-tot));else {for(int i=1;i<=cnt;i++){for(int j=p;j>=v[i];j--)f[j]=max(f[j],f[j-v[i]]+v[i]);}printf("%lld\n",w-(p-f[p]));}}return 0;
}
5. CF632E Thief in a Shop
题目描述
https://www.luogu.com.cn/problem/CF632E
题目概况
来源:Codeforces
洛谷难度: 紫题 \color{purple}紫题 紫题
CF难度: 2400 2400 2400
标签: 01 背包 01背包 01背包
思路分析
基本分析
本题,可读出来每个物品可取无限次 -> 很像完全背包
但是,题目限定恰好取 k k k件物品,十分的恶心(同时也是提升的一次机会。
不妨可以看出这道题中件数是代价,而且每件物品的件数均为1(有点小别扭),所以每件物品的代价均为 1 1 1,
单一性反推多样性
之前做的背包题普遍是靠代价推转移方程算价值,但 d p dp dp只会取极值存储,就如函数的单一性,每个 x x x对应唯一的 y y y ,反之拿 y y y推 x x x是本题不错的选择,因为函数中每个 y y y可以对应多个不同的 x x x。我之所以用函数来解释 d p dp dp,是因为 d p dp dp本身就是函数。
所以一个代价只能对应一个极端价值,但是一个价值能对应多个代价,取代价最小,这个价值的发展空间才能最大化。
思想迸射
一个不成熟的思路油然而生;
状态定义:定义 f i f_i fi当凑得价值为 i i i时,最小所花费的次数
思路一般化处理
先不说 f i f_i fi怎么转移,至少我们可以确定这个 f f f数组是没有后效性的。
假设我们有了一个处理好的数组 f f f,针对 ∀ i ( 1 ≤ i ≤ 100 0 2 ) \forall i(1 \leq i \leq 1000^2) ∀i(1≤i≤10002)
若 f i = k f_i=k fi=k
∴ f i , 绝对可取 \therefore f_i ,绝对可取 ∴fi,绝对可取
若 f i > k f_i>k fi>k
∴ f i ,绝对不可取 \therefore f_i,绝对不可取 ∴fi,绝对不可取
但针对 f i < k f_i <k fi<k
我们不清楚是否能取到,我们需要用一些特殊的处理方式来处理一下。
还来下面将说明这道题,不看 t j tj tj在一个月内都想不出来的处理方法:
- 加入价值为0的物品( l i t t l e t r i c k little trick littletrick)
因为这样在转移方程时,会忽略价值为 0 0 0的物品,加入它们不会让最小凑齐数最小。
∴ 为了让发展空间最大化,将 a 数组 s o r t ( 从小 − > 大 ) ,将 ∀ a i ( 1 ≤ i ≤ n ) − a 1 , 让原有 a 1 变为 0. 最后针对 f i < k 的情况将 i + k a 1 , 凑齐 k 件物品,将 i + k a 1 作为最终结果 \therefore 为了让发展空间最大化,将a数组sort(从小->大),将\forall a_i( 1 \leq i \leq n)-a_1,让原有a_1变为0.最后针对f_i<k的情况将i+ka_1,凑齐k件物品,将i+ka_1作为最终结果 ∴为了让发展空间最大化,将a数组sort(从小−>大),将∀ai(1≤i≤n)−a1,让原有a1变为0.最后针对fi<k的情况将i+ka1,凑齐k件物品,将i+ka1作为最终结果
状态转移方程: f i = min ( f i , min ( f i − a j + 1 ) ) ) ( 1 ≤ i ≤ 100 0 2 ) ( 1 ≤ j ≤ n ) f_i=\min(f_i,\min(f_{i-a_j}+1)))(1 \leq i \leq 1000^2) (1 \leq j \leq n) fi=min(fi,min(fi−aj+1)))(1≤i≤10002)(1≤j≤n)
时间复杂度: O ( 100 0 2 ⋅ n ) \Omicron(1000^2 \cdot n) O(10002⋅n)
若 n 与 1000 同阶 n与1000同阶 n与1000同阶
O ( n 3 ) ≈ 1 e 9 \Omicron(n^3) \approx 1e9 O(n3)≈1e9
5 s − > ≈ 1 e 9 5s -> \approx 1e9 5s−>≈1e9
可以AC
分析结束!
代码实现
#include<bits/stdc++.h>
using namespace std;const int maxn=1e3+10;
const int inf=2e9;
int n,k,cnt;
int a[maxn],f[maxn*maxn],ans[2*maxn*maxn];
int main(){scanf("%d%d",&n,&k);for(int i=1;i<=1000000;i++)f[i]=inf;//状态初始化for(int i=1;i<=n;i++)scanf("%d",&a[i]);sort(a+1,a+n+1);//sort lower->upper//计算凑齐价值为i的最小件数for(int j=1;j<=n;j++){for(int i=a[j]-a[1];i<=1000000;i++){if(f[i-(a[j]-a[1])]==inf)continue;f[i]=min(f[i],f[i-(a[j]-a[1])]+1);//价值为0物品处理}}//f[i]<=k 对于价值物品的补助for(int i=0;i<=1000000;i++){if(f[i]<=k)ans[++cnt]=i+k*a[1];else continue;}//sort printsort(ans+1,ans+cnt+1);for(int i=1;i<=cnt;i++)printf("%d ",ans[i]);return 0;
}