Greedy を考察して、さらに左右から累積和を求める!結構難しい!
類題とか
問題概要
長さ の数列 と、長さ の数列 が与えられます。各 に対して、次の値を求めよ。
- 数列 から を除去してできる長さ の数列 を考えて、数列 を適切に並び替えたときの、 の値の最小値
制約
考えたこと
まずは 通りの値を求める問題をいきなり考えるのではなく、2 つの長さ の数列
があるとして、スコアを最小にするような の並び替え方を考えよう。また、 は小さい順にソートされていると考えて差し支えない。
A もソートしてみる
直感的には「小さい には小さい を、大きい には大きい を」割り当てるのが良さそうに思える。簡単のために の場合で考えてみる。, であるとして、
とする。このとき、 を swap してもスコアが悪化しないことが言える!!!なぜなら
が成り立っているから、ペア を解消することに損はないのだ。以上から、 も も小さい順にソートにして、 の最大値を求めればよいとわかった。
クエリ処理へ
ここまでわかれば、 種類の値を求めることもできる。まず と小さい順にソートしておく (index を復元できるようにしておく)。
そして を除去したときの答えは、次の 2 つの値の最大値として求められる。
これらはそれぞれ「左右からの累積 max 配列」をあらかじめ求めておくことで、高速に答えられる。この前処理は でできる。解法全体としては「数列のソート」がボトルネックで となる。
コード
#include <bits/stdc++.h> using namespace std; int main() { int N; cin >> N; vector<pair<long long,int>> A(N+1); vector<long long> B(N); for (int i = 0; i < N+1; ++i) cin >> A[i].first, A[i].second = i; for (int i = 0; i < N; ++i) cin >> B[i]; sort(A.begin(), A.end()); sort(B.begin(), B.end()); vector<long long> left(N+1, 0), right(N+1, 0); for (int i = 0; i < N; ++i) { left[i+1] = max(left[i], max(A[i].first - B[i], 0LL)); right[i+1] = max(right[i], max(A[N-i].first - B[N-i-1], 0LL)); } vector<int> res(N+1); for (int i = 0; i < N+1; ++i) { int id = A[i].second; res[id] = max(left[i], right[N-i]); } for (int i = 0; i < N+1; ++i) { if (i) cout << " "; cout << res[i]; } cout << endl; }