E - 冒険者と一列のモンスター / Adventurer and a Row of Monsters Editorial
by
MMNMM
モンスターの列に対して、体力 \(d\) から開始してその列のモンスターを順に相手にしたとき、\(\bigl(\)何体のモンスターを倒せるか\(,\) 体力がいくつ残るか\(\bigr)\) の組を返す関数を考えます。 整数の \(2\) つ組 \(p\) について、\(1\) つ目の要素を \(p _ 1\) 、\(2\) つ目の要素を \(p _ 2\) と書くことにします。
モンスターの強さがそれぞれ \((a _ 1,a _ 2,\ldots,a _ k)\) であるような列に対する上の関数を \(f _ {(a _ 1,a _ 2,\ldots,a _ k)}\) と書くことにします。 各クエリで求めるべきものは、\({f _ {(A _ l,A _ {l+1},\ldots,A _ r)}(d)} _ 1\) です。
列 \(A\) と \(B\) を連結した列 \(A{{}+\!\!\!\!{}+{}}B\) について、\(f _ {A{{}+\!\!{}+{}}B}\) を \(f _ A\) と \(f _ B\) で表すことを考えます。 体力 \(d\) で列 \(A\) のモンスターを順に相手すると、\({f _ A(d)} _ 1\) 体のモンスターを倒し、体力が \({f _ A(d)} _ 2\) になります。 このあと列 \(B\) のモンスターを順に相手するので、さらに \({f _ B\left({f _ A(d)} _ 2\right)} _ 1\) 体のモンスターを倒し、体力が \({f _ B\left({f _ A(d)} _ 2\right)} _ 2\) になることがわかります。 このことから、\[f _ {A{{}+\!\!{}+{}}B}(d)=\left({f _ A(d)} _ 1+{f _ B\left({f _ A(d)} _ 2\right)} _ 1,{f _ B\left({f _ A(d)} _ 2\right)} _ 2\right)\] となります。
セグメント木でこの関数を管理することを考えます。 この関数は非負整数から非負整数の \(2\) つ組への関数ですが、クエリに対して答えを出すためには \(0,1,\ldots,C\) の \(C+1\) 個の値に対する関数の値を管理しておけば十分です。
時間計算量は \(O((N+Q\log N)C)\) などになります。
実装例は以下のようになります。
#include <iostream>
#include <vector>
#include <ranges>
#include <atcoder/segtree>
using namespace std;
int main() {
static int N, C, Q;
cin >> N >> C >> Q;
vector<int> A(N);
for (int& a : A) {
cin >> a;
}
// 一体のモンスターからなる列に対応する関数
auto single_monster = [](int a) {
vector<pair<int, int>> ret(C + 1);
// 体力が足りなければ倒せない
for (int i = 0; i < a; ++i) {
ret[i] = {0, i};
}
// 足りたら 1 体倒せる
for (int i = a; i <= C; ++i) {
ret[i] = {1, i - a};
}
return ret;
};
// セグメント木をつくる
atcoder::segtree<vector<pair<int, int>>, [](auto lhs, auto rhs) {
vector<pair<int, int>> ret(C + 1);
for (int i = 0; i <= C; ++i) {
auto [left_count, left_hp] = lhs[i]; // 左の列を先に倒して
auto [right_count, right_hp] = rhs[left_hp]; // 残った体力で右を倒す
ret[i] = {left_count + right_count, right_hp};
}
return ret;
}, [] { // 単位元は空の列
vector<pair<int, int>> ret(C + 1);
for (int i = 0; i <= C; ++i) {
ret[i] = {0, i};
}
return ret;
}> segment_tree(A | views::transform(single_monster) | ranges::to<vector>());
for (int i = 0; i < Q; ++i) {
int t;
cin >> t;
if (t == 1) {
int p, x;
cin >> p >> x;
--p; // 0-indexed にする
segment_tree.set(p, single_monster(x)); // 体力 x のモンスターで更新
} else {
int l, r, d;
cin >> l >> r >> d;
--l; // l, r を 0-indexed 右半開区間にする
cout << segment_tree.prod(l, r)[d].first << endl;
}
}
return 0;
}
from atcoder import segtree
N, C, Q = map(int, input().split())
A = list(map(int, input().split()))
# 一体のモンスターからなる列に対応する関数
def single_monster(a):
return [(0, i) for i in range(a)] + [(1, i - a) for i in range(a, C + 1)]
# 二つの列に対応する関数から連結した列に対応する関数を作る
def concat(left, right):
return [(left[i][0] + right[left[i][1]][0], right[left[i][1]][1]) for i in range(C + 1)]
# セグメント木をつくる
segment_tree = segtree.SegTree(concat, [(0, i) for i in range(C + 1)], [single_monster(a) for a in A])
for i in range(Q):
t, *q = map(int, input().split())
if t == 1:
p, x = q
p -= 1 # 0-indexed にする
segment_tree.set(p, single_monster(x)) # 体力 x のモンスターで更新
else:
l, r, d = q
l -= 1 # l, r を 0-indexed 右半開区間にする
print(segment_tree.prod(l, r)[d][0])
posted:
last update:
