結果
| 問題 |
No.2395 区間二次変換一点取得
|
| コンテスト | |
| ユーザー |
|
| 提出日時 | 2023-07-22 00:51:39 |
| 言語 | C++17 (gcc 13.3.0 + boost 1.87.0) |
| 結果 |
AC
|
| 実行時間 | 837 ms / 2,000 ms |
| コード長 | 6,218 bytes |
| コンパイル時間 | 4,841 ms |
| コンパイル使用メモリ | 262,856 KB |
| 最終ジャッジ日時 | 2025-02-15 17:53:18 |
|
ジャッジサーバーID (参考情報) |
judge5 / judge2 |
(要ログイン)
| ファイルパターン | 結果 |
|---|---|
| other | AC * 20 |
ソースコード
#include <bits/stdc++.h>
using namespace std;
#include <atcoder/all>
using namespace atcoder;
using mint = modint;
#define rep(i, n) for(int i = 0, i##_len = int(n); i < i##_len; ++i)
//【双対セグメント木(M-集合)】
/*
* Dual_segtree<S, F, act, comp, id>(vS v) : O(n)
* 配列 v[0..n) の要素で初期化する.
* 要素は左モノイド作用付き集合 (S, F, act, comp, id) の元とする.
*
* set(int i, S x) : O(log n)
* v[i] = x とする.
*
* S get(int i) : O(log n)
* v[i] を返す.
*
* apply(int l, int r, F f) : O(log n)
* v[l..r) = f( v[l..r) ) とする.
*/
template <class S, class F, S(*act)(F, S), F(*comp)(F, F), F(*id)()>
class Dual_segtree {
int actual_n; // 実際の要素数
int n; // 完全二分木の葉の数(必ず 2 冪)
// S の要素の格納用配列
vector<S> v;
// F の遅延評価用の完全二分木
vector<F> lazy;
// 遅延させていた評価を行う.:O(1)
void eval(int k) {
// 遅延させていた評価がなければ何もしない.
if (lazy[k] == id()) return;
// 子が居れば子に伝搬する.
if (k < n / 2) {
// 左作用を考えているのでこの向きに合成する.
lazy[k * 2] = comp(lazy[k], lazy[k * 2]);
lazy[k * 2 + 1] = comp(lazy[k], lazy[k * 2 + 1]);
}
// 葉なら遅延させずに v の要素に作用させてしまえばいい.
else {
v[k * 2 - n] = act(lazy[k], v[k * 2 - n]);
v[k * 2 + 1 - n] = act(lazy[k], v[k * 2 + 1 - n]);
}
// 子への伝搬を終えたので自身は恒等写像になる.
lazy[k] = id();
}
// k : 注目ノード,[kl..kr) : ノード v[k] が表す区間
void set_sub(int i, S x, int k, int kl, int kr) {
// 葉まで降りてきたら値を代入して帰る.
if (kr - kl == 1) {
v[k - n] = x;
return;
}
// まず自身の評価を行っておく.
eval(k);
// 左右の子を見に行く.
int km = (kl + kr) / 2;
if (i < km) set_sub(i, x, k * 2, kl, km);
else set_sub(i, x, k * 2 + 1, km, kr);
}
// k : 注目ノード,[kl..kr) : ノード v[k] が表す区間
S get_sub(int i, int k, int kl, int kr) {
// 葉まで降りてきたら値を返す.
if (kr - kl == 1) return v[k - n];
// まず自身の評価を行っておく.
eval(k);
// 左右の子を見に行く.
int km = (kl + kr) / 2;
if (i < km) return get_sub(i, k * 2, kl, km);
else return get_sub(i, k * 2 + 1, km, kr);
}
// k : 注目ノード,[kl, kr) : ノード v[k] が表す区間
void apply_sub(int l, int r, F f, int k, int kl, int kr) {
// 範囲外なら何もしない.
if (kr <= l || r <= kl) return;
// 完全に範囲内なら自身の値を更新する.
if (l <= kl && kr <= r) {
if (kr - kl > 1) {
// 左作用を考えているのでこの向きに合成する.
lazy[k] = comp(f, lazy[k]);
}
else {
v[k - n] = act(f, v[k - n]);
}
return;
}
// まず自身の評価を行っておく.
eval(k);
// 一部の範囲のみを含むなら子を見に行く.
int km = (kl + kr) / 2;
apply_sub(l, r, f, k * 2, kl, km);
apply_sub(l, r, f, k * 2 + 1, km, kr);
}
public:
// 配列 v[0..n) の要素で初期化する.
Dual_segtree(vector<S>& v_) : actual_n(v_.size()) {
// verify : https://judge.yosupo.jp/problem/range_affine_point_get
// 要素数以上となる最小の 2 冪を求め,n とする.
n = 1 << (32 - __builtin_clz(actual_n - 1));
// 配列の初期化
v = v_;
v.resize(n);
lazy.assign(n, id());
}
// v[i] = x とする.
void set(int i, S x) {
set_sub(i, x, 1, 0, n);
}
// v[i] を返す.
S get(int i) {
// verify : https://judge.yosupo.jp/problem/range_affine_point_get
return get_sub(i, 1, 0, n);
}
// v[l..r) = f( v[l..r) ) とする.
void apply(int l, int r, F f) {
// verify : https://judge.yosupo.jp/problem/range_affine_point_get
if (l >= r) return;
apply_sub(l, r, f, 1, 0, n);
}
};
//【行列乗算 左作用付き ベクトル 集合】(の改変)
/*
方針は https://yukicoder.me/submissions/894429 の方がわかりやすいと思う.
上記提出では行列と行列の積が重くて TLE したので,必要な成分だけをベタ書きする.
手動でやると大変だが,Wolfram Alpha を使えば,
入力:
{{a,0,0,0,b},{0,c,d,e,0},{0,0,f,0,0},{0,0,g,h,0},{0,0,0,0,1}}.{x,y,z,w,1}
出力:
(b + a x, e w + c y + d z, f z, h w + g z, 1)
および
入力:
{{a,0,0,0,b},{0,c,d,e,0},{0,0,f,0,0},{0,0,g,h,0},{0,0,0,0,1}}.{{i,0,0,0,j},{0,k,l,m,0},{0,0,n,0,0},{0,0,o,p,0},{0,0,0,0,1}}
出力:
{{a i, 0, 0, 0, b + a j}, {0, c k, c l + d n + e o, c m + e p, 0}, {0, 0, f n, 0, 0}, {0, 0, g n + h o, h p, 0}, {0, 0, 0, 0, 1}}
が得られるので,適当に整形すればいい.
同じテクニックを使える問題:ABC256F
自分の提出:https://atcoder.jp/contests/abc256/submissions/32562498
*/
using S = array<mint, 4>;
using F = array<mint, 8>;
S act(F tmpF, S tmpS) {
auto [a, b, c, d, e, f, g, h] = tmpF;
auto [x, y, z, w] = tmpS;
return { b + a * x, e * w + c * y + d * z, f * z, h * w + g * z };
}
F comp(F tmpF, F tmpF2) {
auto [a, b, c, d, e, f, g, h] = tmpF;
auto [i, j, k, l, m, n, o, p] = tmpF2;
return { a * i, b + a * j, c * k, c * l + d * n + e * o, c * m + e * p, f * n, g * n + h * o, h * p };
}
F id() {
return { 1, 0, 1, 0, 0, 1, 0, 1 };
}
#define MatrixLMul_Vector_mset S, F, act, comp, id
int main() {
int N, B, Q;
cin >> N >> B >> Q;
mint::set_mod(B);
// 作用される側は 5 次元ベクトル (X, Y, Z, XZ, 1),作用する側は 5×5 行列
vector<S> ini(N, S{1, 1, 1, 1});
Dual_segtree<MatrixLMul_Vector_mset> seg(ini);
// 本問の操作はこの行列を掛けることに相当する.
F f = { 1, 1, 3, 2, 2, 3, 3, 3 };
// { 1,0,0,0,1 },
// { 0,3,2,2,0 },
// { 0,0,3,0,0 },
// { 0,0,3,3,0 },
// { 0,0,0,0,1 }
while(Q--) {
int l, m, r;
cin >> l >> m >> r;
l--; m--;
// 区間 [l..r) 内のベクトルに行列を掛ける.
seg.apply(l, r, f);
// m 番目のベクトルを得る.
auto [x, y, z, xz] = seg.get(m);
cout << x.val() << " " << y.val() << " " << z.val() << endl;
}
}