甚至到退役都没有系统地学习过这个东西,唉,草台班子SDZX。
到高中毕业也只会这一种。。
不过状态转移方程还是很好写,注意如果要滚掉一维,直接倒序枚举容量即可。
例题 P1048
| for(int i=1;i<=n;++i) |
| { |
| for(int w=m;w>=0;--w) |
| { |
| if(w-a[i]>=0)dp[w]=max(dp[w],dp[w-a[i]]+val[i]); |
| } |
| } |
P1441 一点小变式,但是大概就是把01背包的转移改成了赋一个布尔值,回过头发现还没学的时候做出来一道新生赛的题竟然就是这玩意
| |
| #include<bits/stdc++.h> |
| using namespace std; |
| template<typename T>inline void re(T &x) |
| { |
| x=0;int f=1;char c=getchar(); |
| while(!isdigit(c)){if(c=='-')f=-1;c=getchar();} |
| while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=getchar();} |
| } |
| int MAXVAL; |
| int n,m,a[100],w[100]; |
| bool tag[100]; |
| int ans; |
| inline void dfs(int finished,int st) |
| { |
| if(finished==m||st>n) |
| { |
| if(finished!=m)return ; |
| int cnt=0; |
| for(int i=1;i<=n;++i)if(!tag[i])w[++cnt]=a[i]; |
| vector<int>dp (MAXVAL+10,0); |
| dp[0]=1; |
| for(int i=1;i<=cnt;++i) |
| for(int j=MAXVAL;j>=w[i];--j) |
| if(dp[j-w[i]])dp[j]=1; |
| |
| ans=max(ans,accumulate(dp.begin(),dp.end(),-1)); |
| return ; |
| } |
| for(int i=st;i<=n;++i) |
| { |
| tag[i]=1; |
| dfs(finished+1,i+1); |
| tag[i]=0; |
| } |
| } |
| int main() |
| { |
| re(n),re(m); |
| for(int i=1;i<=n;++i)re(a[i]),MAXVAL+=a[i]; |
| dfs(0,1); |
| cout<<ans; |
| return 0; |
| } |
| |
每个物品可以无限选,那么比较naive的想法就是再在最里面套一层枚举选取个数的循环,但奈何这种时间复杂度较高,想一想有没有更加优秀的做法。
为什么01背包不能正序枚举?因为可能会出现 容量时,我已经用 当前物品做了一次转移,也就是已经被用了,但是后面枚举 这个容量的时候,可能又会从 这个状态 再装一个当前物品进行转移,这样从物理意义上来说,就是用了两次同一个物品,在 01背包的意义下明显是不合法的。
但是这种不合法恰恰就正好是完全背包的要求所在啊!!!所以我们正序枚举就行。
| for(int i=1;i<=n;++i) |
| { |
| for(int j=w[i];j<=m;++j) |
| { |
| dp[j]=max(dp[j],dp[j-w[i]]+val[i]); |
| } |
| } |
不是无限个,可以选多次。
暴力地话就是完全背包的naive做法。复杂度是 的。
| for(int i=1;i<=n;++i) |
| { |
| for(int j=m;j>=0;--j) |
| { |
| for(int cnt=0;cnt<=num[i]&&j-cnt*w[i]>=0;++cnt) |
| { |
| dp[j]=max(dp[j],dp[j-cnt*w[i]]+val[i]*cnt); |
| } |
| } |
| } |
二进制拆分可以减少无意义选取的次数,注意这里要求拆分的方式能够组合出所有数:复杂度
| for(int i=1,tval,tw,tnum;i<=n;++i) |
| { |
| re(tval),re(tw),re(tnum); |
| for(int j=1;j<=tnum;j<<=1) |
| { |
| w[++cnt]=tw*j,val[cnt]=tval*j; |
| tnum-=j; |
| } |
| if(tnum)w[++cnt]=tw*tnum,val[cnt]=tval*tnum; |
| } |
| for(int i=1;i<=cnt;++i) |
| { |
| for(int j=m;j>=w[i];--j) |
| { |
| dp[j]=max(dp[j],dp[j-w[i]]+val[i]); |
| } |
| } |
还有一种比较优秀的单调队列优化,但是今晚害得做一做 cf ,挖个坑。
然而昨天晚上还是没有做 : (
//TODO
例题 P1776
每组里面只能选取一件,直接枚举组数->枚举容量->枚举每个子物品就可以了。
| for(int i=1;i<=t;++i) |
| { |
| for(int j=m;j>=0;--j) |
| { |
| for(int k=1;k<=a[i].cnt;++k) |
| { |
| if(j-a[i].w[k]>=0)dp[j]=max(dp[j],dp[j-a[i].w[k]]+a[i].val[k]); |
| } |
| } |
| } |
多了一维费用,实际上就是两维的背包。
| for(int i=1;i<=n;++i) |
| { |
| cin>>w[i]>>t[i]; |
| for(int j=m;j>=w[i];--j) |
| { |
| for(int k=T;k>=t[i];--k) |
| { |
| dp[j][k]=max(dp[j][k],dp[j-w[i]][k-t[i]]+1); |
| } |
| } |
| } |
例题 1855
言下之意就是存在某些物品 ,如果要选它 ,那么必须先选另外一个指定的物品 。
发现这样的话无非就要么只买主件,要么就同时买其中若干个附件,但是由于这些情况又不能够同时成立,所以就转化成了一个分组背包的模型。
例题 P 1064
| #include<bits/stdc++.h> |
| using namespace std; |
| const int N=4e4+10; |
| int m,n; |
| int weight[N],val[N],son[N][4],cnt[N],q[N]; |
| int num=0; |
| struct group |
| { |
| int w[10],v[10]; |
| int num; |
| void display() |
| { |
| for(int i=1;i<=num;++i) |
| {printf("%d %d\n",w[i],v[i]);} |
| } |
| }a[N]; |
| inline void pre() |
| { |
| cin>>m>>n; |
| for(int i=1;i<=n;++i) |
| { |
| cin>>weight[i]>>val[i]; |
| val[i]*=weight[i]; |
| cin>>q[i]; |
| if(q[i]==0)continue; |
| son[q[i]][++cnt[q[i]]]=i; |
| } |
| |
| for(int i=1;i<=n;++i) |
| { |
| if(q[i])continue; |
| num++; |
| a[num].num++; |
| a[num].w[a[num].num]=weight[i],a[num].v[a[num].num]=val[i]; |
| if(cnt[i]>=1) |
| { |
| a[num].num++; |
| a[num].w[a[num].num]=weight[i]+weight[son[i][1]],a[num].v[a[num].num]=val[i]+val[son[i][1]]; |
| } |
| if(cnt[i]==2) |
| { |
| a[num].num++; |
| a[num].w[a[num].num]=weight[i]+weight[son[i][2]],a[num].v[a[num].num]=val[i]+val[son[i][2]]; |
| a[num].num++; |
| a[num].w[a[num].num]=weight[i]+weight[son[i][1]]+weight[son[i][2]],a[num].v[a[num].num]=val[i]+val[son[i][1]]+val[son[i][2]]; |
| } |
| } |
| } |
| int main() |
| { |
| pre(); |
| vector<int>dp(m+1,-1); |
| dp[0]=0; |
| for(int i=1;i<=num;++i) |
| { |
| for(int j=m;j>=0;--j) |
| { |
| for(int idx=1;idx<=a[i].num;++idx) |
| { |
| if(j-a[i].w[idx]>=0)dp[j]=max(dp[j],dp[j-a[i].w[idx]]+a[i].v[idx]); |
| } |
| } |
| } |
| int ans=0; |
| for(int i=0;i<=m;++i)ans=max(ans,dp[i]); |
| cout<<ans; |
| return 0; |
| } |
顾名思义就是在数上面跑一个背包,以每一个节点为根节点,其子树都是一个独立的背包问题。
然后这个子树算出来的 dp 值作为新的物品价值 来当做这个节点的父节点的待选物,这样对于父节点来说又是一个新的dp问题
例题 P2014 选课
| |
| #include<bits/stdc++.h> |
| using namespace std; |
| inline void ckmax(int &x,int y){if(x<y)x=y;} |
| const int N=400; |
| int n,m; |
| int dp[N][N],s[N]; |
| vector<int> e[N]; |
| inline void pre() |
| { |
| cin>>n>>m; |
| for(int i=1,fa;i<=n;++i) |
| { |
| cin>>fa>>s[i]; |
| e[fa].push_back(i); |
| } |
| } |
| inline void dfs(int x) |
| { |
| for(auto v:e[x]) |
| dfs(v); |
| for(auto v:e[x]) |
| { |
| for(int j=m;j>=1;--j) |
| { |
| for(int k=0;k<=j-1;++k) |
| { |
| ckmax(dp[x][j],dp[x][j-(k+1)]+dp[v][k]+s[v]); |
| } |
| } |
| } |
| } |
| int main() |
| { |
| pre(); |
| dfs(0); |
| int ans=0; |
| for(int i=0;i<=m;++i)ckmax(ans,dp[0][i]); |
| cout<<ans; |
| return 0; |
| } |
1.分组背包,不一定要用结构体把所有情况构造出来,这启示我们可以直接从状态压缩的角度枚举二进制数来进行考虑
2.但是容量的转移并不是传统的加减,因为回合结束还有一个容量自动增加,所以还有一个增量的过程,要额外做一个映射再进行转移。
| #include<bits/stdc++.h> |
| using namespace std; |
| typedef long long ll; |
| int n; |
| const ll INF=1e15; |
| ll val[10],w[10]; |
| pair<ll,ll> sum(int sta) |
| { |
| ll ansval=0,answ=0; |
| int cnt=1; |
| while(sta) |
| { |
| ansval+=(sta&1ll)*val[cnt]; |
| answ+=(sta&1ll)*w[cnt]; |
| sta>>=1; |
| cnt++; |
| } |
| return {ansval,answ}; |
| } |
| int las[1000]; |
| |
| int main() |
| { |
| cin>>n; |
| vector< vector<ll> > dp(20000,vector<ll>(100,-INF)); |
| for(int i=1;i<=61;++i)las[i+i/10+2]=i; |
| for(int i=0;i<=70;++i)if(!las[i])las[i]=999; |
| |
| for(int j=1;j<=5;++j)cin>>val[j]; |
| for(int j=1;j<=5;++j)cin>>w[j]; |
| for(int j=24;j>=0;--j) |
| { |
| for(int sta=0;sta<32;++sta) |
| { |
| pair<ll,ll> p=sum(sta); |
| ll nowval=p.first,noww=p.second; |
| if(j+noww!=24)continue; |
| dp[1][j]=max(dp[1][j],nowval); |
| } |
| } |
| for(int i=2;i<=n;++i) |
| { |
| for(int j=1;j<=5;++j)cin>>val[j]; |
| for(int j=1;j<=5;++j)cin>>w[j]; |
| for(int j=70;j>=0;--j) |
| { |
| for(int sta=0;sta<32;++sta) |
| { |
| pair<ll,ll> p=sum(sta); |
| ll nowval=p.first,noww=p.second; |
| if(j+noww>70)continue; |
| if(j+noww<=69) |
| dp[i][j]=max(dp[i][j],dp[i-1][las[j+noww]]+nowval); |
| else |
| for( |
| #include<bits/stdc++.h> |
| #define int long long |
| using namespace std; |
| int n,x,k; |
| const int N=1e4+10; |
| struct item |
| { |
| int w,v,c; |
| const bool operator<(item tmp){return c<tmp.c;} |
| }a[N]; |
| |
| const int INF=1e9; |
| signed main() |
| { |
| ios::sync_with_stdio(0); |
| cin.tie(0),cout.tie(0); |
| cin>>n>>x>>k; |
| for(int i=1;i<=n;++i) |
| { |
| cin>>a[i].w>>a[i].v>>a[i].c; |
| } |
| sort(a+1,a+n+1); |
| |
| a[0].c=0; |
| vector<vector<vector<int>>> dp(2,vector<vector<int>>(x+10,vector<int>(2,-INF))); |
| int p=0; |
| dp[0][0][0]=0; |
| for(int i=1;i<=n;++i) |
| { |
| p^=1; |
| for(int j=0;j<=x;++j) |
| { |
| if(a[i].c!=a[i-1].c) |
| { |
| if(j-a[i].w>=0)dp[p][j][1]=max(dp[p^1][j-a[i].w][0],dp[p^1][j-a[i].w][1])+a[i].v+k; |
| dp[p][j][0]=max(dp[p^1][j][0],dp[p^1][j][1]); |
| } |
| else |
| { |
| if(j-a[i].w>=0)dp[p][j][1]=max(dp[p^1][j-a[i].w][0]+a[i].v+k,dp[p^1][j-a[i].w][1]+a[i].v); |
| dp[p][j][1]=max(dp[p][j][1],dp[p^1][j][1]); |
| dp[p][j][0]=dp[p^1][j][0]; |
| } |
| |
| } |
| } |
| int ans=0; |
| for(int i=0;i<=x;++i)ans=max(ans,max(dp[p][i][0],dp[p][i][1])); |
| cout<<ans; |
| return 0; |
| }int t=62;t<=70;++t)dp[i][j]=ma```c |
| ```x(dp[i][j],dp[i-1][t]+nowval); |
| |
| } |
| } |
| } |
| ll ans=0; |
| for(int i=0;i<=70;++i)ans=max(ans,dp[n][i]); |
| cout<<ans; |
| return 0; |
| } |
| |
很明显的一个背包问题,但是带有限制,也就是所谓的“颜色”。
由于物品的考虑顺序并不会影响最终的答案,所以可以把所有物品都按照颜色排序,这样就保证了 同一种颜色都相邻
这时候定义 为前 个达到 容量,当前这个颜色选过没有。
注意这个时候转移可能有些不同,比如 实际上可以是当前这个不选,之前选了一个的情况,所以转移方程可能有点不同。
然后不能开三维,但是直接kick一维似乎也不太行??那就把第一维滚掉吧。
| #include<bits/stdc++.h> |
| #define int long long |
| using namespace std; |
| int n,x,k; |
| const int N=1e4+10; |
| struct item |
| { |
| int w,v,c; |
| const bool operator<(item tmp){return c<tmp.c;} |
| }a[N]; |
| |
| const int INF=1e9; |
| signed main() |
| { |
| ios::sync_with_stdio(0); |
| cin.tie(0),cout.tie(0); |
| cin>>n>>x>>k; |
| for(int i=1;i<=n;++i) |
| { |
| cin>>a[i].w>>a[i].v>>a[i].c; |
| } |
| sort(a+1,a+n+1); |
| |
| a[0].c=0; |
| vector<vector<vector<int>>> dp(2,vector<vector<int>>(x+10,vector<int>(2,-INF))); |
| int p=0; |
| dp[0][0][0]=0; |
| for(int i=1;i<=n;++i) |
| { |
| p^=1; |
| for(int j=0;j<=x;++j) |
| { |
| if(a[i].c!=a[i-1].c) |
| { |
| if(j-a[i].w>=0)dp[p][j][1]=max(dp[p^1][j-a[i].w][0],dp[p^1][j-a[i].w][1])+a[i].v+k; |
| dp[p][j][0]=max(dp[p^1][j][0],dp[p^1][j][1]); |
| } |
| else |
| { |
| if(j-a[i].w>=0)dp[p][j][1]=max(dp[p^1][j-a[i].w][0]+a[i].v+k,dp[p^1][j-a[i].w][1]+a[i].v); |
| dp[p][j][1]=max(dp[p][j][1],dp[p^1][j][1]); |
| dp[p][j][0]=dp[p^1][j][0]; |
| } |
| |
| } |
| } |
| int ans=0; |
| for(int i=0;i<=x;++i)ans=max(ans,max(dp[p][i][0],dp[p][i][1])); |
| cout<<ans; |
| return 0; |
| } |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效