AOJ 1341 - Longest Chain
解法
動的2次元セグ木とかで解けそうだけど、ICPC でデータ構造の問題はチームメイトに投げるため違う解き方をする。
とりあえずソートができるので3次元 -> 2次元になる。
1次元のときの LIS と違うのは、長さ L の末尾として最適なものが定まらない(全順序じゃないから)ということ。
なので、末尾の最小値の代わりに、最適になりうる集合を持つことにする。
この集合は、例えば {(y1, z1), (y2, z2), ...} としたら y1 < y2 < ... かつ z1 > z2 > ... となるように保持する。
y1 < y2 かつ z1 < z2 なる (y2, z2) は最適にならないからこれでOK。
あとは普通の LIS の要領で二分探索する(できる)。
(y, z) を長さ L の列の末尾にできる条件は、dp[L - 1] の要素で yi < y を満たす最大の yi に対して (yi, zi) < (y, z) であることが集合の持ち方から言える。
(y, z) を挿入するときは集合の条件を壊さないようにすること。
計算量は O(n(logn)^2)。
同じ y の値がくると面倒なので、最初に (y, z) の降順に並べておくと楽。
ソースコード
#include <bits/stdc++.h> using namespace std; int a, b; constexpr int C = ~(1 << 31); constexpr int M = (1 << 16) - 1; int r() { a = 36969 * (a & M) + (a >> 16); b = 18000 * (b & M) + (b >> 16); return (C & ((a << 16) + b)) % 1000000; } struct point { int y, z; point(int y_, int z_) : y(y_), z(z_) {} bool operator<(point const& p) const { return y < p.y; } }; int main() { ios_base::sync_with_stdio(false); cin.tie(0); int m, n; while(cin >> m >> n >> a >> b, n + m) { vector<tuple<int, int, int>> ps(n + m); for(int i = 0; i < m; ++i) { int x, y, z; cin >> x >> y >> z; ps[i] = make_tuple(x, -y, -z); } for(int i = m; i < n + m; ++i) { const int x = r(), y = r(), z = r(); ps[i] = make_tuple(x, -y, -z); } sort(begin(ps), end(ps)); vector<point> ps2; for(int i = 0; i < n + m; ++i) { ps2.emplace_back(-get<1>(ps[i]), -get<2>(ps[i])); } int ans = 0; vector<set<point>> dp(n + m + 1); dp[0].emplace(0, 0); for(auto const& p : ps2) { auto check = [&] (int i) { auto it = dp[i].lower_bound(p); if(it == dp[i].begin()) return false; it = prev(it); return p.y > it->y && p.z > it->z; }; int lb = 0, ub = n + m; while(ub - lb > 1) { const int mid = (ub + lb) >> 1; (check(mid) ? lb : ub) = mid; } auto it = dp[ub].lower_bound(p); while(it != dp[ub].end() && it->z >= p.z) { it = dp[ub].erase(it); } if(it == dp[ub].begin() || prev(it)->z > p.z) { dp[ub].insert(p); } ans = max(ans, ub); } cout << ans << endl; } }