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结尾的”。这一点在做最长公共子序列和最长公共子串的时候可以得到很好的理解。

浙公网安备 33010602011771号