配点: 500 点 / 実行時間制限: 2 sec / メモリ制限: 1024 MB / Difficulty: 1284
問題概要
長さ の整数列
が与えられるので、
個のクエリを処理せよ。なお、
番目のクエリは以下の通り。
- 整数
が与えられる。数列
の部分列であって、狭義単調増加であり、かつ全ての要素が
以下であるようなものの長さの最大値を求めよ。
制約
- 入力は全て整数。
考察
ある数列の部分列であって増加列であるもののうち、その長さが最大のものを最長部分増加列(Longest Increasing Subsequence 、略して LIS)と言う。
LIS は動的計画法によって求められるが、ここではその詳細な実装は扱わないので、以下の記事を参照のこと。
ところで、動的計画法の際に用いる DP テーブルの定義は以下のものであった。
長さ
の LIS を作るときの末尾の要素の最小値(存在しなければ
)
ここで、クエリを の小さい順に先読みしておき、
と更新しながら、
を更新した時点で、
を満たす最大の
を二分探索で求め、それをそのままクエリ の答えとしてやればよい。
実装においては、 DP テーブルを「 の長さの LIS」で考えているので、
をキーとした
upper_bound
を用いればよいだろう。
実装例
#include <bits/stdc++.h> using namespace std; #define all(x) (x).begin(), (x).end() #define rep(i, start, end) for (auto i = (start); (i) < (end); (i)++) constexpr long long INFL = 1e+18; using ll = long long; using Pair_int = pair<int, int>; // ======================================== // int main() { int N, Q; cin >> N >> Q; vector<int> A(N); rep(i, 0, N) cin >> A[i]; vector<vector<Pair_int>> queries(N); rep(i, 0, Q) { int R, X; cin >> R >> X; R--; queries[R].push_back({X, i}); } vector<int> ans(Q); vector<ll> dp(N, INFL); rep(i, 0, N) { auto itr = lower_bound(all(dp), A[i]); if (itr == dp.end()) { dp.push_back(A[i]); } else { *itr = A[i]; } for (auto &&query : queries[i]) { ans[query.second] = upper_bound(all(dp), query.first) - dp.begin(); } } for (auto &&x : ans) { cout << x << endl; } return 0; }
実装時間: 40分
コメント
LIS の応用問題みたいな感じ。 LIS さえ知っていればそこまで難しくはなさそう。
また、 LIS を二次元上で考えると視覚的に分かりやすそうだなとも思った。