mini notes

競技プログラミングの解法メモを残していきます。

AtCoder蟻本 初級編 2-3 動的計画法 ④01ナップサック問題その2

ABC032 D - ナップサック問題

D - ナップサック問題

概要

  • 0/1ナップサック
  • 制約が3種類
制約


1 \leq N \leq 30
1 \leq W \leq10^9
1 \leq v_{i} \leq10^9
1 \leq w_{i}  \leq10^9


1 \leq N \leq 200
1 \leq W \leq10^9
1 \leq v_{i} \leq10^9
1 \leq w_{i}  \leq 1000


1 \leq N \leq 200
1 \leq W \leq10^9
1 \leq v_{i} \leq1000
1 \leq w_{i}  \leq 10^9

方針

実質3問。

いわゆる半分全列挙。
荷物を前半分、後半分の2つに分け、それぞれですべての荷物の入れ方で重さの合計、価値の合計のペアをベクトルに格納する。
前半分の荷物で作成したベクトルをv1、後半分をv2とする。

その後、v1の要素を1つずつ取り出し、v2の要素の中で、
「両者の重さの合計がW以下で価値が最大のもの」を探す。
全てのv1の要素でそれを行い、価値が最大のものが答え。

探し方は愚直にやるとO(v1.size * v2.size)だが、ソートしてv1を重い方から、v2を軽い方から見てゆくとO(max(v1.size, v2.size))になる(はず)。



w_{i}の上限が小さいので、dp[i] : 重さ合計がiのときにできる価値合計の最大値としてDPする。



v_{i}の上限が小さいので、dp[i] : 価値合計がiのときにできる重さ合計の最小値としてDPする。

感想

下記解答を参考にしました。
Submission #1381183 - AtCoder Beginner Contest 032

②、③はdp[i][j]のように2次元で組むとセグフォする(メモリオーバー?)。
1次元DPでやるときは、dp[i]をiが大きい方から遷移させると、同じ荷物で何度も更新することがない。
逆に同じ荷物で何度も更新したい場合はdp[i]をiが小さい方から遷移させる。

解答

Submission #1381183 - AtCoder Beginner Contest 032

#include <bits/stdc++.h>
 
#define FOR(i,a,b) for(int i=(a);i<(b);i++)
#define rep(i,n) FOR(i,0,n)
#define RFOR(i,a,b) for(int i=(a)-1;i>=(b);i--)
#define rrep(i,n) RFOR(i,n,0)
 
using namespace std;
 
typedef long long ll;
typedef unsigned long long ull;
typedef pair<ll, ll> pll;
 
int main()
{
	cin.tie(0);
	ios::sync_with_stdio(false);
 
	int n, max_w;
	cin >> n >> max_w;
 
	ll v[n], w[n];
	rep(i, n) cin >> v[i] >> w[i];
 
	ll mv = 0;
	ll mw = 0;
	rep(i, n){
		mv = max(mv, v[i]);
		mw = max(mw, w[i]);
	}
 
	if(n <= 30){
		if(n == 1){
			cout << (w[0] <= max_w ? v[0] : 0) << endl;
			return 0;
		}
 
		int n1 = n / 2;
		int n2 = n - n1;
 
		vector<pll> v1, v2;
		for(ll i = 0; i < (1 << n1); i++){
			ll tv = 0;
			ll tw = 0;
			for(ll j = 0; j < n1; j++){
				if((i >> j) & 1){
					tv += v[j];
					tw += w[j];
				}
 
				if(tw <= max_w){
					// cout << "push1: " << tw << " " << tv << endl;
					v1.push_back((pll){tw, tv});
				}
			}
		}
 
		for(ll i = 0; i < (1 << n2); i++){
			ll tv = 0;
			ll tw = 0;
			for(ll j = n1; j < n1 + n2; j++){
				if((i >> (j - n1)) & 1){
					tv += v[j];
					tw += w[j];
				}
 
				if(tw <= max_w){
					// cout << "push2: " << tw << " " << tv << endl;
					v2.push_back((pll){tw, tv});
				}
			}
		}
 
		sort(v1.begin(), v1.end());
		sort(v2.begin(), v2.end());
 
		ll m1 = v1.size();
		ll m2 = v2.size();
		ll ans = 0LL;
		ll ma = 0LL;
		ll j = 0LL;
		for(ll i = m1-1; i >= 0; i--){
			pll p1 = v1[i];
			while(j < m2 && p1.first + v2[j].first <= max_w){
				// if(p1.second + v2[j].second > ans) cout << p1.first << " " << p1.second << " " << v2[j].first << " " << v2[j].second << endl;
				ma = max(ma, v2[j].second);
				j++;
			}
			ans = max(ans, p1.second + ma);
		}
 
		cout << ans << endl;
		return 0;
 
	}else if(mv <= 1000){
 
		ll INF = 1e15;
		mv = 200010;
 
		ll dp2[mv+1];
		rep(j, mv+1) dp2[j] = INF;
		dp2[0] = 0;
 
		for(int i = 0; i < n; i++){
			for(int j = mv; j >= 0; j--){
				if(j - v[i] >= 0){
					dp2[j] = min(dp2[j], dp2[j-v[i]]+w[i]);
				}
			}
		}
 
		for(ll i = mv; i >= 0; i--){
			if(0 <= dp2[i] && dp2[i] <= max_w){
				cout << i << endl;
				return 0;
			}
		}
	}else{
		ll INF = 1e15;
		mw = 200010;
		ll dp3[mw+1];
		rep(j, mw+1) dp3[j] = 0;
		for(int i = 0; i < n; i++){
			for(int j = mw; j >= 0; j--){
				if(j - w[i] >= 0){
					dp3[j] = max(dp3[j], dp3[j-w[i]]+v[i]);
				}
			}
		}
 
		ll ans = 0;
		for(ll i = 0; i <= max_w; i++){
			ans = max(ans, dp3[i]);
		}
 
		cout << ans << endl;
	}
}