ABC145 E - All-you-can-eat
May 6, 2020
問題
考えたこと(WA)
最後の1つをT-1
に取ると決めてしまえば、N-1(最後にとる1つを除く残りの料理)
で食べ終わるのにT-1
かかる美味しさの最大値というナップサック問題に落とし込めそう。
計算量はO(NT)でO(10^6)
なので間に合う
あとは最後に食べる奴は一番美味しさがでかい奴でいいかな?
これは嘘解
以下のような入力でWAになる
3 1000
2 40
500 20
500 12
上記の解法だと, 最後に2 40
を食べるとなって、T-1(=999)
で残りの2つを食べるということになるが、これだと残り1つのどちらかが食べれなくなってしまう。
最後に500 20
を食べてしまえば、T-1(=999)
で残りの2つも食べれて全部食べれる
解説を見て
最後に食べるのは一意には特定できないので、全探索する。
しかし最後に食べるのを全探索して、さらに残りのやつでT-1
までに食べれる最大値を求めるとO(10^3 * 10^3 * 10^3)
でTLEになってしまう。
予めDPテーブルを作成しておけば、計算量を落とすことができる。
ここでDPテーブルを2つ用意する必要がある。
DP1[i][j]
: 0~i番目までの料理の中で食べ終わるのにj分かかるので最大の満足度DP2[i][j]
: i~N番目までの料理の中で食べ終わるのにj分かかるので最大の満足度
これを用意しておいて、全ての料理を全探索していくi
番目の料理をT-1
分に食べる時の満足度はDP1[i-1][j]
とDP[i+1][t-1-j]
とi番目の料理の満足度
の和となる(j
は0~T
)
これで全ての料理を全探索してあげればO(NT)=O(10^6)
で計算できる
提出したコード
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define rep(i, n) for (ll i = 0; i < (ll)(n); ++i)
#define erep(i, n) for (ll i = 0; i <= (ll)(n); ++i)
#define FOR(i,a,b) for (ll i = (a); i < (ll)(b); ++i)
#define EFOR(i,a,b) for (ll i = (a); i <= (ll)(b); ++i)
void chmax(ll& a, ll b) { a = max(a, b); }
void chmin(ll& a, ll b) { a = min(a, b); }
using P = pair<int, int>;
int main() {
int n,t; cin >> n >> t;
vector<P> ba;
rep(i, n) {
int a,b;
cin >> a >> b;
ba.push_back(make_pair(b,a));
}
vector<vector<int>> dp1(n+1, vector<int>(t, 0));
vector<vector<int>> dp2(n+1, vector<int>(t, 0));
vector<int> ans1(n,0);
vector<int> ans2(n,0);
rep(i, n) {
rep(j, t) {
if(j >= ba[i].second) {
if(i > 0) dp1[i][j] = max(dp1[i-1][j-ba[i].second] + ba[i].first, dp1[i-1][j]);
else dp1[i][j] = ba[i].first;
} else {
if(i > 0) dp1[i][j] = dp1[i-1][j];
}
ans1[i] = max(ans1[i], dp1[i][j]);
}
}
for(int i=n-1; i >= 0; i--) {
rep(j, t) {
if(j >= ba[i].second) {
dp2[i][j] = max(dp2[i+1][j-ba[i].second] + ba[i].first, dp2[i+1][j]);
} else {
dp2[i][j] = dp2[i+1][j];
}
ans2[i] = max(ans2[i], dp2[i][j]);
}
}
int ans = 0;
rep(i, n) {
int tmpans = 0;
rep(j, t) {
if(i == 0) {
tmpans = max(tmpans, dp2[i + 1][j]);
} else if(i == n-1) {
tmpans = max(tmpans, dp1[i - 1][j]);
} else {
tmpans = max(tmpans, dp1[i - 1][j] + dp2[i + 1][t - 1 - j]);
}
}
ans = max(ans, tmpans+ba[i].first);
}
cout << ans << endl;
return 0;
}
解説動画を見て
https://www.youtube.com/watch?v=aDDV_WBwzTM
ちょっと工夫すればDPテーブル1つで済む
ある料理の組み合わせ(最後の注文がT-1
以下になる)は、並び替えができて最後の注文は食べるのに一番時間がかかる料理で固定できる。
これをすると、最後に食べる料理にかかる時間がj
分かかるときの満足度は
食べるのにかかる時間がj
より小さい料理の中でT-1
分までに食べ終わる中での最大の満足度と最後に食べる料理の満足度の和になる
入力を食べるのにかかる時間の昇順ソートで並べておくとi
番目の料理を最後に食べるとしたら、i-1
までの中での料理の中でT-1
分までに食べ終わる中での最大の満足度を求めてあげればいい
(つまり, 1つ前の解法でのDP1, DP2
のうちDP1
だけあればいい)
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define rep(i, n) for (ll i = 0; i < (ll)(n); ++i)
#define erep(i, n) for (ll i = 0; i <= (ll)(n); ++i)
#define FOR(i,a,b) for (ll i = (a); i < (ll)(b); ++i)
#define EFOR(i,a,b) for (ll i = (a); i <= (ll)(b); ++i)
#define chmax(x,y) x = max(x,y);
#define chmin(x,y) x = min(x,y);
using P = pair<int, int>;
int dp[3005][3005];
int main() {
int n,t; cin >> n >> t;
vector<P> ab(n);
rep(i, n) cin >> ab[i].first >> ab[i].second;
// 一番時間がかかるのを最後に食べるという方針にする
// 昇順ソートしておけば、i番目を最後に食べるときに、i-1まででt-1までに食べれる最大量を求めるので済む
sort(ab.begin(), ab.end());
int ans = 0;
rep(i, n) {
rep(j, t) {
chmax(dp[i+1][j], dp[i][j]);
int nj = j+ab[i].first;
if(nj < t) chmax(dp[i+1][nj], dp[i][j] + ab[i].second);
}
int now = dp[i][t-1] + ab[i].second;
chmax(ans, now);
}
cout << ans << endl;
return 0;
}