01背包方案数
简单的01背包
题目大意:
有n个物品和m的容量,求所有区间lr内物品的拿取种类和,结果对998244353取模。
n,s,ai都是3000 。
思路:
拿01背包算种类复杂度是ns。再去枚举所有的区间,复杂度是n2*ns。
void solve(){
cin >> n >> m ;
rep(i , 1 , n) cin >> a[i] ;
ll ans = 0 ;
rep(L , 1 , n)
rep(s , 1 , n - L + 1){
vector<vector<ll> > dp(n + 1 , vector<ll>(m + 1)) ;
dp[s - 1][0] = 1 ;//init
rep(i , s , s + L - 1)
rep(j , 0 , m){
if(j < a[i]) dp[i][j] = dp[i - 1][j] ;
else dp[i][j] = (dp[i - 1][j] + dp[i - 1][j - a[i]]) % mod ;
}
ans = (ans + dp[s + L - 1][m] ) % mod ;
}
cout << ans ;
}
然后我们发现在做1->n的背包时实际上已经把1->i的种类都算过了,就是说我们没有必要再去枚举右区间,只要枚举左区间就行。这样的时间复杂度就变成了n2*s 。
void solve(){
cin >> n >> m ;
rep(i , 1 , n) cin >> a[i] ;
ll ans = 0 ;
rep(s , 1 , n){
vct<vct<ll> > dp(n + 1 , vct<ll> (m + 1)) ;
dp[s - 1][0] = 1 ;
rep(i , s , n){
rep(j , 0 , m){
if(j < a[i])dp[i][j] = dp[i - 1][j] ;
else dp[i][j] = (dp[i - 1][j] + dp[i - 1][j - a[i]]) % mod ;
}
ans = (ans + dp[i][m]) % mod ;
}
}
cout << ans << "\n" ;
}
继续优化,将普通01背包的状态表示更改成以i结尾的所有区间种数和。
那么考虑一下状态转移,应该还是考虑拿或者不拿第i个物品,所以dp[i,j] 还是从拿ai和不拿ai处转移过来的。dp[i,j] = dp[i - 1,j - ai] + dp[i - 1, j] 。看起来没有什么变化但是dp方程的意义发送了改变。再就是初始化,dp[i,0] = i + 1 ;
这个初始化比较难以理解,但其实就是以i结尾有几个区间就是几。因为dp[i,j]表示的是i+1个区间的和。(有点难想)
这样就把整体复杂度降低到了ns!
void solve(){
cin >> n >> m ;
rep(i , 1 , n) cin >> a[i] ;
ll ans = 0 ;
vct<vct<ll> > dp(n + 1 , vct<ll> (m + 1)) ;
rep(i , 1 , n){
dp[i - 1][0] = i ;
rep(j , 1 , m){
if(j < a[i])dp[i][j] = dp[i - 1][j] ;
else dp[i][j] = (dp[i - 1][j] + dp[i - 1][j - a[i]]) % mod ;
}
ans = (ans + dp[i][m]) % mod ;
}
cout << ans << "\n" ;
}
小结:
dp的两种常用状态表示:“i以前的”,“以i结尾的”。这一点在做最长公共子序列和最长公共子串的时候可以得到很好的理解。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 零经验选手,Compose 一天开发一款小游戏!
· 因为Apifox不支持离线,我果断选择了Apipost!
· 通过 API 将Deepseek响应流式内容输出到前端