教育的で楽しい
問題概要
長さ の数列
が与えられる。数列中の部分列 (連続でなくてよい) であって、連続する自然数となっているもののうち、最長のものを求めよ。
また、それを復元せよ (添字を答える)。
ex: (3, 3, 4, 7, 5, 6, 8) -> (3, 4, 5, 6) で答えは (1, 3, 5, 6) など
制約
考えたこと
一目見て Greedy にできるタイプの問題ではなくて、DP っぽい走査が必要になりそう。
- dp[ i ][ j ] := 最初の i 個までのうち、最後が j (座標圧縮しておく) であるようなものの最長の長さ
とすると、 a[ i ] の対応する番号 (座標圧縮) が k のとき
j != k のとき
- chmax(dp[ i + 1 ][ j ], dp[ i ][ j ]) (j != k)
j == k のとき
もし k > 0 であって、a の中に a[ k ] - 1 が含まれているならば、
- chmax(dp[ i + 1 ][ j ], dp[ i ][ j ] + 1)
そうでない場合には
- dp[ i + 1 ][ j ] = 1
という風になる。
in-place に
さて、よく見ると、i -> i + 1 の更新において、dp[ i ][ j ] の中の更新がおきるような j は一箇所だけであることがわかる。よって、第一添字の i は不要で、in-place な DP で解くことができる。
これによって計算量も に落ちる。
#include <iostream> #include <sstream> #include <cstdio> #include <cstdlib> #include <cmath> #include <ctime> #include <cstring> #include <string> #include <vector> #include <stack> #include <queue> #include <deque> #include <map> #include <set> #include <bitset> #include <numeric> #include <utility> #include <iomanip> #include <algorithm> #include <functional> #include <unordered_map> 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; } int N; vector<long long> a; void solve() { // 座標圧縮 vector<long long> vs; map<long long, vector<int>> pl; for (int i = 0; i < a.size(); ++i) { vs.push_back(a[i]); pl[a[i]].push_back(i); } sort(vs.begin(), vs.end()); vs.erase(unique(vs.begin(), vs.end()), vs.end()); // DP int s = vs.size(); vector<long long> dp(s+1, 0); for (int i = 0; i < N; ++i) { int it = lower_bound(vs.begin(), vs.end(), a[i]) - vs.begin(); if (it == 0 || a[i] != vs[it-1] + 1) dp[it] = 1; else chmax(dp[it], dp[it-1] + 1); } long long res = 0; long long val = -1; for (int i = 0; i <= s; ++i) if (chmax(res, dp[i])) val = vs[i]; // 復元 vector<int> ans; int end = N; for (int i = 0; i < res; ++i) { int it = lower_bound(pl[val].begin(), pl[val].end(), end) - pl[val].begin(); --it; ans.push_back(pl[val][it]); end = pl[val][it]; --val; } reverse(ans.begin(), ans.end()); cout << res << endl; for (int i = 0; i < res; ++i) { if (i) cout << " "; cout << ans[i]+1; } cout << endl; } int main() { while (cin >> N) { a.resize(N); for (int i = 0; i < N; ++i) cin >> a[i]; solve(); } }