解法.二分探索 + RMQ
解法が思いつかなかったので 解説 を参考にした.
任意の に対して が最大値として選ばれて,かつ,条件を満たす区間の数を とする.ただし,最大値が複数ある場合は添字が最も小さいものが選ばれるものとする.関数 が計算できると答えは となるので を求めることを考える.
の値に寄与する区間を とすると, は次の条件を満たす.
条件1.
条件2.
条件3.
条件4.
条件1, 2は が最大値として選ばれているという条件で,条件2, 3は問題文にある制約の絶対値を書き直したものである.観察として,区間 の部分区間 も の値に寄与する区間である.したがって, が上の条件を満たす極大な区間のとき,
となる.
の場合は となるので と仮定する.
まず初めに条件1を満たす区間の左端で最も左にあるものを求める.上の観察より,左端を としたときに条件 を満たすかどうかという性質は単調になるので に関する二分探索を行う.各 に対する最大値関数の計算は のRange Maximum Query に答える関数 を使用する. を区間木で実装すると,前処理に 時間,各クエリに 時間かかるので,極大な左端を求めるのに全体で 時間かかる.しかし, の値は変更しないので Sparse Table で実装すると,前処理に 時間,各クエリに 時間で全体で 時間で計算可能である(実装では区間木 のみ を選択. Sparse Tableの実装を追加).同様に条件2を満たす極大な区間の右端も 時間で求まる.
次に,条件3, 4について考える.上で求めた条件1, 2を満たす極大な区間を とする.条件3, 4を同時に満たす区間の数を数えるのは大変なので, の部分区間で次の条件を満たす極大な区間をそれぞれ求める(条件3と3'は同じ).
条件3'.
条件4'.
これは,上の方法と同様に の Range Maximum Query に答える関数 を求めて と をそれぞれ別に二分探索で求めると同じ計算時間で求まる.このとき,条件3'と条件4'を満たす極大な区間をそれぞれ とすると,
となる.
計算時間:
#include <bits/stdc++.h> using namespace std; using ll = long long; /*******************************************************************************************/ template<typename Monoid> struct SegmentTree { using T = typename Monoid::value_type; std::size_t sz; std::vector<T> d; explicit SegmentTree(std::size_t n = 0) { for (sz = 1; sz < n; ) sz <<= 1; d.resize(2 * sz, Monoid::unit()); } template<typename InputIterator> SegmentTree(InputIterator first, InputIterator last) { resize(first, last); } template<typename InputIterator> void resize(InputIterator first, InputIterator last) { std::size_t n = std::distance(first, last); for (sz = 1; sz < n; ) sz <<= 1; d.resize(2 * sz, Monoid::unit()); std::copy(first, last, d.begin() + sz); initialize(); } void fill(const T &value) { std::fill(d.begin() + sz, d.end(), value); initialize(); } void initialize() { for (int i = sz - 1; 0 < i; --i) d[i] = Monoid::op(d[i << 1], d[i << 1 | 1]); } // d[idx] = value void update(std::size_t idx, const T &value) { for (d[idx += sz] = value; idx >>= 1; ) d[idx] = Monoid::op(d[idx << 1], d[idx << 1 | 1]); } // d[l] * d[l + 1] * ... * d[r - 1] T accumulate(std::size_t l, std::size_t r) const { T res_l = Monoid::unit(), res_r = Monoid::unit(); for (l += sz, r += sz; l < r; l >>= 1, r >>= 1) { if (l & 1) res_l = Monoid::op(res_l, d[l++]); if (r & 1) res_r = Monoid::op(d[--r], res_r); } return Monoid::op(res_l, res_r); // for non-commutative } // d[idx] T operator[](std::size_t idx) const { return d[sz + idx]; } }; template<typename T> struct max_commutative_monoid { using value_type = T; static constexpr T unit() { return std::numeric_limits<T>::min(); } static T op(const T &lhs, const T &rhs) { return std::max(lhs, rhs); } }; /*******************************************************************************************/ class Solver { public: Solver(int _n) : num_case(_n) {} void input() { cin >> N >> K; c.resize(N); d.resize(N); for (auto &c_i : c) cin >> c_i; for (auto &d_i : d) cin >> d_i; } void solve(); void print() { cout << "Case #" << num_case << ": " << num_pair << '\n'; } private: const int num_case; int N, K; vector<int> c, d; ll num_pair; using Seg = SegmentTree<max_commutative_monoid<int>>; array<Seg, 2> rmq; void init() { num_pair = 0; rmq[0].resize(c.begin(), c.end()); rmq[1].resize(d.begin(), d.end()); } int rangeToLeftLessEqual(const int idx, const int begin, const int end, const int UB) { int lb = begin - 1, ub = end; while (1 < ub - lb) { int mid = (lb + ub) / 2; if (rmq[idx].accumulate(mid, end + 1) <= UB) ub = mid; else lb = mid; } return (rmq[idx].accumulate(ub, end + 1) <= UB) ? ub : end + 1; } int rangeToRightLessEqual(const int idx, const int begin, const int end, const int UB) { int lb = begin, ub = end + 1; while (1 < ub - lb) { int mid = (lb + ub) / 2; if (rmq[idx].accumulate(begin, mid + 1) <= UB) lb = mid; else ub = mid; } return (rmq[idx].accumulate(begin, lb + 1) <= UB) ? lb : begin - 1; } }; void Solver::solve() { init(); for (int i = 0; i < N; ++i) { if (c[i] + K < d[i]) continue; int L = i, R = i; if (i != 0) L = rangeToLeftLessEqual(0, 0, i - 1, c[i] - 1); if (i != N - 1) R = rangeToRightLessEqual(0, i + 1, N - 1, c[i]); ll l = rangeToLeftLessEqual(1, L, i, c[i] + K); ll r = rangeToRightLessEqual(1, i, R, c[i] + K); num_pair += (i - l + 1) * (r - i + 1); if (d[i] < c[i] - K) { l = rangeToLeftLessEqual(1, L, i, c[i] - K - 1); r = rangeToRightLessEqual(1, i, R, c[i] - K - 1); num_pair -= (i - l + 1) * (r - i + 1); } } } int main() { cin.tie(0); ios::sync_with_stdio(false); int T; cin >> T; for (int i_case = 1; i_case <= T; ++i_case) { Solver s(i_case); s.input(); s.solve(); s.print(); } return 0; }
計算時間:
#include <bits/stdc++.h> using namespace std; using ll = long long; /*******************************************************************************************/ template<typename IdempotentMonoid> struct SparseTable { using T = typename IdempotentMonoid::value_type; std::size_t sz = 0; std::vector<std::vector<T>> d; // d[p][i] = op(a[i], a[i + 1], ..., a[i + 2^p - 1]) std::vector<size_t> log2; // log2[i] = floor(log_2(i)) SparseTable() {} explicit SparseTable(std::size_t n) : sz(n) { initializeLog2(); d.resize(log2[sz] + 1, std::vector<T>(sz, IdempotentMonoid::unit())); updateTable(); } template<typename InputIterator> SparseTable(InputIterator first, InputIterator last) { resize(first, last); } void resize(const size_t n) { sz = n; initializeLog2(); d.resize(log2[sz] + 1, std::vector<T>(sz, IdempotentMonoid::unit())); updateTable(); } template<typename InputIterator> void resize(InputIterator first, InputIterator last) { sz = std::distance(first, last); initializeLog2(); d.resize(log2[sz] + 1, std::vector<T>(sz, IdempotentMonoid::unit())); std::copy(first, last, d[0].begin()); updateTable(); } void initializeLog2() { log2.resize(sz + 1); // log2[0] = 1 for (size_t i = 2; i <= sz; ++i) log2[i] = log2[i >> 1] + 1; } void updateTable() { for (size_t p = 1; p < d.size(); p++) for (size_t i = 0; i + (1 << p) <= sz; i++) d[p][i] = IdempotentMonoid::op(d[p - 1][i], d[p - 1][i + (1 << (p - 1))]); } T accumulate(std::size_t l, std::size_t r) const { const size_t p = log2[r - l]; return IdempotentMonoid::op(d[p][l], d[p][r - (1 << p)]); } void update(std::size_t idx, const T &value) { d[0][idx] = value; } T operator[](std::size_t idx) const { return d[0][idx]; } T& operator[](std::size_t idx) { return d[0][idx]; } }; template<typename T> struct max_monoid { using value_type = T; static constexpr T unit() { return std::numeric_limits<T>::min(); } static T op(const T &lhs, const T &rhs) { return std::max(lhs, rhs); } }; /*******************************************************************************************/ class Solver { public: Solver(int _n) : num_case(_n) {} void input() { cin >> N >> K; c.resize(N); d.resize(N); for (auto &c_i : c) cin >> c_i; for (auto &d_i : d) cin >> d_i; } void solve(); void print() { cout << "Case #" << num_case << ": " << num_pair << '\n'; } private: const int num_case; int N, K; vector<int> c, d; ll num_pair; array<SparseTable<max_monoid<int>>, 2> rmq; void init() { num_pair = 0; rmq[0].resize(c.begin(), c.end()); rmq[1].resize(d.begin(), d.end()); } int rangeToLeftLessEqual(const int idx, const int begin, const int end, const int UB) { int lb = begin - 1, ub = end; while (1 < ub - lb) { int mid = (lb + ub) / 2; if (rmq[idx].accumulate(mid, end + 1) <= UB) ub = mid; else lb = mid; } return (rmq[idx].accumulate(ub, end + 1) <= UB) ? ub : end + 1; } int rangeToRightLessEqual(const int idx, const int begin, const int end, const int UB) { int lb = begin, ub = end + 1; while (1 < ub - lb) { int mid = (lb + ub) / 2; if (rmq[idx].accumulate(begin, mid + 1) <= UB) lb = mid; else ub = mid; } return (rmq[idx].accumulate(begin, lb + 1) <= UB) ? lb : begin - 1; } }; void Solver::solve() { init(); for (int i = 0; i < N; ++i) { if (c[i] + K < d[i]) continue; int L = i, R = i; if (i != 0) L = rangeToLeftLessEqual(0, 0, i - 1, c[i] - 1); if (i != N - 1) R = rangeToRightLessEqual(0, i + 1, N - 1, c[i]); ll l = rangeToLeftLessEqual(1, L, i, c[i] + K); ll r = rangeToRightLessEqual(1, i, R, c[i] + K); num_pair += (i - l + 1) * (r - i + 1); if (d[i] < c[i] - K) { l = rangeToLeftLessEqual(1, L, i, c[i] - K - 1); r = rangeToRightLessEqual(1, i, R, c[i] - K - 1); num_pair -= (i - l + 1) * (r - i + 1); } } } int main() { cin.tie(0); ios::sync_with_stdio(false); int T; cin >> T; for (int i_case = 1; i_case <= T; ++i_case) { Solver s(i_case); s.input(); s.solve(); s.print(); } return 0; }
いろいろ細かいことが考えられないので大変だった.グローバル空間を汚したくないのでクラスで実装したけど,競プロだと実装時間も順位の要因になるので悩ましい.今後のGCJではテンプレートにして試してみる.
後で Sparse Table での実装をしてみる.構築に 時間でできるのもあるらしいのでそれも(Range Minimum Query - Qiita).RmQの表記を使いたかったけど Maximum だった・・・.