关于一类特殊范围下的 0-1 背包优化算法
这是一个 0-1 背包模板题,但物品数
朴素背包 DP 的复杂度为
由于物品大小的范围很小,于是对其进行分组,相同大小的物品分为一组。然后对于同一组的物品,按价值从大到小排序,然后求前缀和。设
这样就转换为了分组背包了,设
若第
注意到第一维可以去掉,设
考虑去掉
对
观察我们最终得到的式子,不难发现转移实际上是
补充:之所以要对
当
这显然不是一个凹函数,而当
这很明显就是一个凹函数,即使
于是套个决策单调性分治优化的板子就写完了,这么做的时间复杂度为
参考代码如下:
#include<bits/stdc++.h>
#define cin_fast ios::sync_with_stdio(false) , cin.tie(0) , cout.tie(0)
//#define int long long
#define in(a) a = read()
#define PII pair<int , int>
using namespace std;
typedef long long ll;
const int N = 1e6 + 5 , mod = 998244353;
const int inf = 0x3f3f3f3f;
const long long INF = 0x3f3f3f3f3f3f3f3f;
inline int read() {
int x = 0;
char ch = getchar();
bool f = 0;
while('9' < ch || ch < '0') f |= ch == '-' , ch = getchar();
while('0' <= ch && ch <= '9') x = (x << 3) + (x << 1) + ch - '0' , ch = getchar();
return f ? -x : x;
}
int t , x;
ll f[N] , dp[N];
vector<ll>a[N];
ll w(int i , int j) {
if(i == j) return f[j * t - t + x];
return f[j * t - t + x] + a[t][min(i - j , (int)a[t].size()) - 1];
}
void solve(int l , int r , int optl , int optr) {
int mid = (l + r) >> 1 , id = mid * t - t + x , opt = optl;
for(int i = optl ; i <= min(optr , mid) ; i ++) {
if(w(mid , i) >= dp[id]) dp[id] = w(mid , i) , opt = i;
}
if(l < mid) solve(l , mid - 1 , optl , opt);
if(mid < r) solve(mid + 1 , r , opt , optr);
}
signed main() {
//cin_fast;
int n , k;
in(n) , in(k);
for(int i = 1 ; i <= n ; i ++) {
int v , w;
in(v) , in(w);
a[v].emplace_back(w);
}
for(int i = 1 ; i <= 300 ; i ++) {
sort(a[i].begin() , a[i].end() , greater<int>());
for(int j = 1 ; j < a[i].size() ; j ++) a[i][j] += a[i][j - 1];
}
for(t = 1 ; t <= min(300 , k) ; t ++) {
if(a[t].empty()) continue;
for(x = 0 ; x < t ; x ++) {
int cnt = (k - x) / t + 1;
solve(1 , cnt , 1 , cnt);
}
for(int j = 0 ; j <= k ; j ++) f[j] = dp[j];
}
for(int i = 1 ; i <= k ; i ++) cout << f[i] << ' ';
return 0;
}