I - Takahashi in Narrow Road 解説
by
MMNMM
\(i\) 番目の高橋くんの座標 \(X _ i\) に対して、\(X _ i=X ^ {\prime} _ i+i\) なる整数 \(X ^ {\prime} _ i\) を考えます。
このとき、操作は次のように言い換えられます。
\(T,G\) が与えられる。\(X ^ {\prime} _ T\leq G\) なら \(X ^ {\prime} _ i\in\lbrack X ^ {\prime} _ T,G\rbrack\) かつ \(T\leq i\) なるすべての \(i\) について、そうでないなら \(X ^ {\prime} _ i\in\lbrack G,X ^ {\prime} _ T\rbrack\) かつ \(i\leq T\) なるすべての \(i\) について、それぞれコストを \(|X ^ {\prime} _ i-G|\) 支払って \(X ^ {\prime} _ i\) を \(G\) に変更する。
制約より、\(X ^ {\prime} _ i\) は \(i\) についてつねに単調です。 そのため、上の操作で書き換えられる \(i\) も単一の区間を成します。 また、\(X ^ {\prime} _ i\) の値の条件から支払うコストは \(\displaystyle\sum _ {l\leq i\leq r}X ^ {\prime} _ i-G\times(r-l+1)\) もしくは \(\displaystyle G\times(r-l+1)-\sum _ {l\leq i\leq r}X ^ {\prime} _ i\) の形になります。
よって、この問題は以下のような方針で解くことができます。
- 区間代入および区間和・最小値・最大値を処理する遅延セグメント木などを用いて、上の操作を直接行う。
- \(X ^ {\prime} _ i\) の値が一致している区間をまとめて、
std::map
などの平衡二分木を用いて管理する。
実装例は以下のようになります。
この実装例では、遅延セグメント木を用いて実装を行っています。
#include <iostream>
#include <optional>
#include <limits>
#include <vector>
#include <atcoder/lazysegtree>
int main() {
using namespace std;
constexpr unsigned offset{100000000};
unsigned N;
cin >> N;
// セグメント木をつくる
auto segment_tree = [] (const auto N) {
// (区間の長さ, 区間内の総和, 区間内の最小値, 区間内の最大値) を管理する
struct segment_tree_value {
unsigned length;
unsigned long sum;
unsigned min, max;
};
// 区間への代入
using assign_type = optional<unsigned>;
atcoder::lazy_segtree<segment_tree_value, [](const auto& lhs, const auto& rhs) -> segment_tree_value {
// 区間をいい感じに合併する
const auto& [l_len, l_sum, l_min, l_max]{lhs};
const auto& [r_len, r_sum, r_min, r_max]{rhs};
return {l_len + r_len, l_sum + r_sum, min(l_min, r_min), max(l_max, r_max)};
}, []() -> segment_tree_value {
// 単位元は長さ 0 の区間
return {0U, 0UL, offset * 2, 0U};
}, assign_type, [](const auto& f, const auto x) {
// 代入クエリがあるなら、それで埋める
return f ? segment_tree_value{x.length, static_cast<unsigned long>(*f) * x.length, *f, *f} : x;
}, [](const auto& f, const auto &x){
return f ? f : x;
}, []{return std::nullopt;}> result(N);
for (unsigned i{}; i < N; ++i) {
unsigned X;
cin >> X;
X += offset;
X -= i;
result.set(i, {1, X, X, X});
}
return result;
}(N);
unsigned Q;
cin >> Q;
unsigned long ans{};
for (unsigned i{}; i < Q; ++i) {
unsigned T, G;
cin >> T >> G;
--T;
G += offset;
G -= T;
// 現在地は長さ 1 の区間を取ってきたときの 合計 = 最小値 = 最大値
const auto now_position{segment_tree.get(T).max};
if (now_position < G) { // いま、目的地より西側にいるなら
// 現在地より東、目的地より西にいる高橋くんを列挙し、
const auto upper_bound{segment_tree.max_right(T, [G](const auto& v){return v.max < G;})};
const auto& [length, sum, min, max]{segment_tree.prod(T, upper_bound)};
// かかる移動回数を求める
ans += static_cast<unsigned long>(length) * G - sum;
// 間にいる高橋くん全員を移動させる
segment_tree.apply(T, upper_bound, G);
} else { // 東側にいるなら、
// 現在地より西、目的地より東にいる高橋くんを列挙し、
const auto it{segment_tree.min_left(T + 1, [G](const auto& v){return v.min > G;})};
const auto& [length, sum, min, max]{segment_tree.prod(it, T + 1)};
// かかる移動回数を求める
ans += sum - static_cast<unsigned long>(length) * G;
// 間にいる高橋くん全員を移動させる
segment_tree.apply(it, T + 1, G);
}
}
cout << ans << endl;
return 0;
}
投稿日時:
最終更新: