つい最近 CodeQUEEN 決勝 E 問題でも出てきた「最大値の最大化」典型テクニック!!!!
問題概要
長さ の 2 つの数列 と が与えられる。今、互いに disjoint な区間群
をとる ( は自由)。ただし、区間の長さの総和がちょうど となるようにする必要がある。つまり、
を満たす必要がある。そのような区間群に対して、
の値の最大値を求めよ。
制約
解法
区間を分割していく、いつもの DP。単純にやると の計算量となるやつ。しかし「最大値の最大化」典型が使える。
が成り立つことに注目する。このことから、次のような DP が組める
先頭から 個の要素のみを考える。その中ですでに選んだ区間の長さの総和が (右端が閉じていない場合も含み、その場合は右端を閉じない限りは 1 ずつ増えていく) である場合について、
dp[i][k][0]
← 最後の区間の右端が閉じている状態dp[i][k][1]
← 最後の区間の右端が閉じておらず、左端が である状態dp[i][k][2]
← 最後の区間の右端が閉じておらず、左端が である状態dp[i][k][3]
← 最後の区間の右端が閉じておらず、左端が である状態dp[i][k][4]
← 最後の区間の右端が閉じておらず、左端が である状態
についての、スコアの最大値
こうすることで、DP は直前の状態との比較のみで実現できるようになり、計算量は となる。
コード
#include <bits/stdc++.h> using namespace std; template<class T> inline bool chmax(T& a, T b) { if (a < b) { a = b; return 1; } return 0; } template<class T> inline bool chmin(T& a, T b) { if (a > b) { a = b; return 1; } return 0; } const long long INF = 1LL<<60; void solve() { int N, K; cin >> N >> K; vector<long long> a(N), b(N); for (int i = 0; i < N; ++i) cin >> a[i]; for (int i = 0; i < N; ++i) cin >> b[i]; // 0: non, 1: ++, 2: +-, 3: -+, 4: -- vector dp(K+2, vector(5, -INF)); dp[0][0] = 0; for (int r = 0; r < N; ++r) { vector nex(K+2, vector(5, -INF)); for (int k = 0; k <= K; ++k) { // 0 -> 0 chmax(nex[k][0], dp[k][0]); chmax(nex[k+1][0], dp[k][0] + abs(a[r] - b[r]) * 2); // 0 -> 1, 2, 3, 4 chmax(nex[k+1][1], dp[k][0] + a[r] + b[r]); chmax(nex[k+1][2], dp[k][0] + a[r] - b[r]); chmax(nex[k+1][3], dp[k][0] - a[r] + b[r]); chmax(nex[k+1][4], dp[k][0] - a[r] - b[r]); // 1, 2, 3, 4 keep for (int p = 1; p <= 4; ++p) chmax(nex[k+1][p], dp[k][p]); // 1, 2, 3, 4 -> 0 chmax(nex[k+1][0], dp[k][1] - a[r] - b[r]); chmax(nex[k+1][0], dp[k][2] - a[r] + b[r]); chmax(nex[k+1][0], dp[k][3] + a[r] - b[r]); chmax(nex[k+1][0], dp[k][4] + a[r] + b[r]); } swap(dp, nex); } cout << dp[K][0] << endl; } int main() { int T; cin >> T; while (T--) solve(); }