これは楽しい!!!
順番をうまく決めれば、全部の情報を持たなくても、個数のみの情報に落とせる系。
問題概要
要素の数列 と、 の二次元数列 が与えられる。
- 個の の中から 個を選び、
- 残りの 個の index の中から 個分、 の行ベクトルを選び、どの列も共有しないように列の index を選ぶ
こうして得られる合計 個の整数の総和の最大値を求めよ。
制約
考えたこと
要は の長方形の各列について、どの 2 つの列も横方向に並ばないようにしつつ
- 最終列以外は 1 個の要素を選び
- 最終列は 個の要素を選び
総和を最大化する問題となる。 が小さいので bitDP ができる。難しいのは はとても大きくなりうるので、以下のような素朴な bitDP では間に合わない。
- dp[ i ][ bit ][ num ] := i 行目まで見て、最終列以外は bit に対応する列の index をすでに選んでいて、最終列は num 個の要素をすでに選んでいる場合についての最大値
この bit DP は自然だけど、 かかってしまう。
最終列をソート
最終列以外についてどの行の要素を選ぶか決めると、最終列については残った行のうち、値が大きい順に 個選べば良いことがわかる。このことから、
- 最終列の値が大きい順にソートしておくと
- i 個目の要素を見ているときに、bit のみの情報から、最終列の要素をとるべきかどうかが決まる
ということがわかる。よって、DP の情報量を落として
- dp[ i ][ bit ] := i 行目まで見て、最終列以外は bit に対応する列の index をすでに選んでいる状態についての最大値
として解くことができる。これなら計算量は で済む。
#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; int N, P, K; vector<long long> a; vector<vector<long long>> s; long long solve() { vector<int> ids(N); iota(ids.begin(), ids.end(), 0); sort(ids.begin(), ids.end(), [&](int i, int j) { return a[i] > a[j];}); vector<vector<long long>> dp(N+1, vector<long long>(1<<P, -INF)); dp[0][0] = 0; for (int i = 0; i < N; ++i) { for (int bit = 0; bit < (1<<P); ++bit) { int con = __builtin_popcount(bit); if (i - con < K) chmax(dp[i+1][bit], dp[i][bit] + a[ids[i]]); else chmax(dp[i+1][bit], dp[i][bit]); for (int j = 0; j < P; ++j) { if (bit & (1<<j)) continue; int nbit = bit | (1<<j); chmax(dp[i+1][nbit], dp[i][bit] + s[ids[i]][j]); } } } return dp[N][(1<<P)-1]; } int main() { ios::sync_with_stdio(false); cin.tie(0); cin >> N >> P >> K; a.resize(N); s.assign(N, vector<long long>(P, 0)); for (int i = 0; i < N; ++i) cin >> a[i]; for (int i = 0; i < N; ++i) for (int j = 0; j < P; ++j) cin >> s[i][j]; cout << solve() << endl; }