priority_queue で動的に 個出力する問題作りたいな〜と思ってぼんやりストックしてた問題と一緒だった!!!!!!
問題概要
- 個の要素からなる数列
- 個の要素からなる数列
- 個の要素からなる数列
が与えられる。 からそれぞれ 1 個ずつとりだして和をとった値は 個考えられるが、それらを大きい順に 個出力せよ。
制約
考えたこと (editorial 解法 3)
はそれぞれ大きい順にソートしておく。まず一番大きいのは それぞれの最大値をとったもの で決まりである。ここから priority_queue をうまく用いる。
この次に大きいものは
の 3 つの候補があるが、それぞれをキューに push しておく。このときにキューの先頭にあるものが、2 番目に大きい値になる。
同様にその 2 番目に大きい値を pop して来て、それに対し
- A のみ index を 1 個進めて足したもの
- B のみ index を 1 個進めて足したもの
- C のみ index を 1 個進めて足したもの
の 3 通りの値をキューに push していく。そしてこの時点でキューの先頭にあるものが 3 番目の値になる。以後これを繰り返して行く。注意点として、「一度 push したものは二度 push しない」という管理が必要となる。
計算量は、
- キューから pop する回数が 回
- その 回それぞれについて、キューの push するのは 3 回以下なので、キューに push する回数も多くても 以下
ということで、計算量は となる。
#include <iostream> #include <vector> #include <algorithm> #include <queue> #include <set> using namespace std; using Data = pair<long long, vector<int> >; int main() { vector<long long> N(3); int K; for (int i = 0; i < 3; ++i) cin >> N[i]; cin >> K; vector<vector<long long> > v(3); for (int iter = 0; iter < 3; ++iter) { v[iter].resize(N[iter]); for (int i = 0; i < N[iter]; ++i) cin >> v[iter][i]; sort(v[iter].begin(), v[iter].end(), greater<long long>()); } priority_queue<Data> que; set<Data> se; que.push(Data(v[0][0] + v[1][0] + v[2][0], vector<int>({0, 0, 0}))); for (int k = 0; k < K; ++k) { auto c = que.top();que.pop(); cout << c.first << endl; // 次の三候補 for (int iter = 0; iter < 3; ++iter) { if (c.second[iter] + 1 < N[iter]) { long long sum = c.first - v[iter][c.second[iter]] + v[iter][c.second[iter] + 1]; auto num = c.second; num[iter]++; auto d = Data(sum, num); // すでに push されたもの以外 if (!se.count(d)) se.insert(d), que.push(d); } } } }
解法 2: 探索候補を絞る考え方 (editorial 解法 1)
全部の組合せは 通りあって、これをすべて試していては間に合わないのだが、実はすごく上手い方法がある。
まず、 の各要素の合計値としてありうるものは 個列挙することができる。そしてこの 個の値のうち、上位 個にも入らないやつは、合計した後に上位 個以内に入ることはありえない。よって上位 個のみを残して残りは捨ててしまってよい!!!
よって、さらに
- の各要素の合計値としてありうるものの上位 個
- の各要素
との全組合せを改めて列挙することができて、それらの上位 個を求めれば良い。計算量は 個の要素をソートする部分がボトルネックになっていて、 となる。
ちなみに、このような感じで探索範囲を絞る考え方をする類題として、以下の問題がある。
#include <iostream> #include <vector> #include <algorithm> using namespace std; int main() { int X, Y, Z, K; cin >> X >> Y >> Z >> K; vector<long long> A(X), B(Y), C(Z); for (int i = 0; i < X; ++i) cin >> A[i]; for (int i = 0; i < Y; ++i) cin >> B[i]; for (int i = 0; i < Z; ++i) cin >> C[i]; // A + B の上位 K 個 vector<long long> AB; for (int i = 0; i < X; ++i) for (int j = 0; j < Y; ++j) AB.push_back(A[i] + B[j]); sort(AB.begin(), AB.end(), greater<long long>()); AB.resize(K); // AB と C vector<long long> ABC; for (int i = 0; i < AB.size(); ++i) for (int j = 0; j < Z; ++j) ABC.push_back(AB[i] + C[j]); sort(ABC.begin(), ABC.end(), greater<long long>()); // 出力 for (int k = 0; k < K; ++k) cout << ABC[k] << endl; }
さらに
もっと色々解法があって、すごく面白い!!!!!!
editorial がメッチャ丁寧!!!!!!