提出 #71536145


ソースコード 拡げる

// ================================================================
// AtCoder Library Practice Contest
// K - Range Affine Range Sum
// (URL: https://atcoder.jp/contests/practice2/tasks/practice2_k)
// TypeScript (Deno) Submission
// ================================================================
/**
 * 遅延評価セグメント木 (Lazy Segment Tree)
 * 長さNの配列に対して、区間全要素への作用と区間のモノイド積の計算をO(log N)で行えます。
 * ただし、以下が定義できることが求められます。
 * - 配列の要素の型 S
 * - 配列の2要素のモノイド積を表す演算 op: (a: S, b: S) => S (※結合律の成立と単位元の存在が必要)
 * - opの単位元 e: S
 * - "作用"を行う関数がパラメーターとして求める情報の型 F
 * - 値配列のある要素sと遅延配列の同位置の要素fを受け取り、f適用後のsの値を返す関数 mapping: (s: S, f: F) => S
 * - mappingのfに渡すとsを変更しないような要素 id: F
 * - mapping(mapping(sum, old), new)とmapping(sum, c)が等しくなるようなcを返す関数 composition: (new: F, old: F) => F
 */
class LazySegmentTree<S, F> {
    /** 単位元 */
    #e: S;
    /** モノイド演算を表す関数 */
    #op: (a: S, b: S) => S;
    /** 作用を表す関数 */
    #mapping: (s: S, f: F) => S;
    /** 作用の単位元 */
    #id: F;
    /** 作用の合成を表す関数 */
    #composition: (newF: F, oldF: F) => F;
    /** セグメント木の高さ */
    #log: number;
    /** セグメント木のサイズ */
    #size: number;
    /** セグメント木のコンストラクタに渡されたサイズ */
    #originalSize: number;
    /** セグメント木の内部配列 */
    #data: S[];
    /** 遅延配列 */
    #lazy: F[];

    /**
     * 遅延評価セグメント木の新しいインスタンスを生成します。
     * @param e - 単位元
     * @param op - モノイド演算を表す関数
     * @param mapping - 作用を表す関数
     * @param id - 作用の単位元
     * @param composition - 作用の合成を表す関数
     * @param size - セグメント木のサイズ
     * @param [initialValues] - 初期値の配列(sizeに満たない分はeで埋められます)
     */
    constructor(
        e: S,
        op: (a: S, b: S) => S,
        mapping: (s: S, f: F) => S,
        id: F,
        composition: (newF: F, oldF: F) => F,
        size: number,
        initialValues?: S[],
    ) {
        // e, op, mapping, id, compositionはそのまま保存
        this.#e = e;
        this.#op = op;
        this.#mapping = mapping;
        this.#id = id;
        this.#composition = composition;
        this.#originalSize = size;
        // sizeは与えられたsize以上の最小の2冪に設定
        this.#log = Math.ceil(Math.log2(size));
        this.#size = 2 ** this.#log;
        // lazyを初期化
        this.#lazy = new Array(this.#size * 2).fill(id);
        // data配列を初期化
        this.#data = new Array(this.#size * 2).fill(e);
        // initialValuesが与えられた場合、data配列の後半にセット
        if (initialValues) {
            for (let i = 0; i < initialValues.length; i++) {
                this.#data[this.#size + i] = initialValues[i];
            }
            // 前半を構築 (initialValuesが与えられなかった場合はeのままなので飛ばされる)
            for (let i = this.#size - 1; i > 0; i--) {
                this.#data[i] = this.#op(
                    this.#data[i * 2],
                    this.#data[i * 2 + 1],
                );
            }
        }
    }
    /**
     * `tree`の`index`番目の要素に、操作`f`を作用させます。
     * @param index - 要素のインデックス (0-based)
     * @param f - 作用
     * @private
     */
    #all_apply(index: number, f: F): void {
        this.#data[index] = this.#mapping(this.#data[index], f);
        if (index < this.#size) {
            this.#lazy[index] = this.#composition(f, this.#lazy[index]);
        }
    }
    /**
     * lazyの`index`番目に格納されている遅延タグを子ノードに伝播させ、lazy[index]を初期化します。
     * @param index - ノードのインデックス
     * @private
     */
    #push(index: number): void {
        const l = index * 2;
        const r = index * 2 + 1;
        // lに伝播
        this.#all_apply(l, this.#lazy[index]);
        // rに伝播
        this.#all_apply(r, this.#lazy[index]);
        // lazy[index]を初期化
        this.#lazy[index] = this.#id;
    }
    /**
     * treeの`index`番目のノードを、treeの2つの子ノードから計算し更新します。
     * @param index - ノードのインデックス
     * @private
     */
    #update(index: number): void {
        this.#data[index] = this.#op(
            this.#data[index * 2],
            this.#data[index * 2 + 1],
        );
    }
    /**
     * 範囲[l, r)と共通部分を持つが[l, r)に収まっていないノードについて、
     * 遅延タグを葉まで伝播させます。
     * @param l - 区間の左端 (0-based, 含む)
     * @param r - 区間の右端 (0-based, 含まない)
     * @private
     */
    #pushToLeaves(l: number, r: number): void {
        const left = l + this.#size;
        const right = r + this.#size;
        // 木の高さから1までループを回せばよい
        for (let i = this.#log; i > 0; i--) {
            // left側のノードを伝播
            if (((left >> i) << i) !== left) {
                const leftNode = left >> i;
                this.#push(leftNode);
            }
            // right側のノードを伝播
            if (((right >> i) << i) !== right) {
                const rightNode = (right - 1) >> i;
                this.#push(rightNode);
            }
        }
    }
    /**
     * 範囲[l, r)と共通部分を持つが[l, r)に収まっていないノードについて、
     * 変更されたtreeの子ノードの情報をもとに親ノードの値を(根まで)再計算します。
     * @param l - 区間の左端 (0-based, 含む)
     * @param r - 区間の右端 (0-based, 含まない)
     * @private
     */
    #updateFromLeaves(l: number, r: number): void {
        const left = l + this.#size;
        const right = r + this.#size;
        for (let i = 1; i <= this.#log; i++) {
            // left側のノードを更新
            if (((left >> i) << i) !== left) {
                const leftNode = left >> i;
                this.#update(leftNode);
            }
            // right側のノードを更新
            if (((right >> i) << i) !== right) {
                const rightNode = (right - 1) >> i;
                this.#update(rightNode);
            }
        }
    }
    /**
     * [l, r)の区間に作用fを作用させます。
     * @param l - 区間の左端 (0-based, 含む)
     * @param r - 区間の右端 (0-based, 含まない)
     * @param f - 作用
     */
    apply(l: number, r: number, f: F): void {
        // lとrに対応する葉ノードの位置を求める
        let left = l + this.#size;
        let right = r + this.#size;
        // 1. 前処理: 範囲に関係する部分の遅延をすべて出し切る
        this.#pushToLeaves(l, r);
        // 2. 区間[l, r)に作用fを作用させる
        while (left < right) {
            if (left % 2 === 1) {
                this.#all_apply(left, f);
                left++;
            }
            if (right % 2 === 1) {
                right--;
                this.#all_apply(right, f);
            }
            left = Math.floor(left / 2);
            right = Math.floor(right / 2);
        }
        // 3. 後処理: 変更があった部分の親ノードを再計算する
        this.#updateFromLeaves(l, r);
    }
    /**
     * 半開区間[l, r)のモノイド積を計算して返します。
     * @param l - 区間の左端 (0-based, 含む)
     * @param r - 区間の右端 (0-based, 含まない)
     * @returns 指定された区間のモノイド積
     */
    query(l: number, r: number): S {
        // lとrに対応する葉ノードの位置を求める
        let left = l + this.#size;
        let right = r + this.#size;
        // 1. 前処理: 遅延させていたタグをすべて計算する
        this.#pushToLeaves(l, r);
        // 2. 区間[l, r)のモノイド積を計算する
        let res_left = this.#e;
        let res_right = this.#e;
        while (left < right) {
            if (left % 2 === 1) {
                res_left = this.#op(res_left, this.#data[left]);
                left++;
            }
            if (right % 2 === 1) {
                right--;
                res_right = this.#op(this.#data[right], res_right);
            }
            left = Math.floor(left / 2);
            right = Math.floor(right / 2);
        }
        // 3. 結果を返す
        return this.#op(res_left, res_right);
    }
    /**
     * 半開区間[l, r)のモノイド積で、lを固定したときに条件fnを満たす最大のrを返します。
     * - fn(e)はtrueを返す必要があり、これを満たさない場合は例外がスローされます。
     * - lは0以上size以下である必要があり、これを満たさない場合は例外がスローされます。
     * @param l - 区間の左端 (0-based, 含む)
     * @param fn - 条件を表す関数
     * @returns 条件を満たす最大のr
     */
    maxRight(l: number, fn: (s: S) => boolean): number {
        // fn(e)がtrueであることを確認
        if (!fn(this.#e)) {
            throw new Error("fn(e) must be true");
        }
        // lの範囲チェック
        if (l < 0 || l > this.#originalSize) {
            throw new Error("l must be in range [0, size]");
        }
        // lがsizeと等しい場合、sizeを返す
        if (l === this.#originalSize) {
            return this.#originalSize;
        }
        // lに対応する葉ノードの位置を求める
        const left = l + this.#size;
        // 前処理: l側の遅延させていたタグは先にすべて計算しておく
        this.#pushToLeaves(l, l + 1);
        // モノイド積の初期値
        let product = this.#e;
        // maxRightの探索
        let pos = left;
        do {
            // posが右の子ノードである場合、親ノードに移動
            while (pos % 2 === 0) pos = Math.floor(pos / 2);
            // 今のブロック(tree[pos])を足すと条件を満たさなくなるかチェックする
            if (!fn(this.#op(product, this.#data[pos]))) {
                // 満たさない -> 境界はこのブロックの範囲 -> 葉まで降りていく
                while (pos < this.#size) {
                    // 遅延タグを伝播させる
                    this.#push(pos);
                    // 左の子に降りる
                    pos = pos * 2;
                    // 今のブロックを足しても条件を満たすなら、足して右の子へ
                    if (fn(this.#op(product, this.#data[pos]))) {
                        product = this.#op(product, this.#data[pos]);
                        pos++;
                    }
                }
                // 葉ノードに到達したら、そのindexを結果として返す
                return pos - this.#size;
            }
            // 今のブロックを足しても条件を満たすなら、足して次へ
            product = this.#op(product, this.#data[pos]);
            pos++;
        } while ((pos & -pos) !== pos); // posが2冪になるまで繰り返す
        // すべて足しても条件を満たす場合、sizeを返す
        return this.#originalSize;
    }
    /**
     * 半開区間[l, r)のモノイド積で、rを固定したときに条件fnを満たす最小のlを返します。
     * - fn(e)はtrueを返す必要があり、これを満たさない場合は例外がスローされます。
     * - rは0以上size以下である必要があり、これを満たさない場合は例外がスローされます。
     * @param r - 区間の右端 (0-based, 含まない)
     * @param fn - 条件を表す関数
     * @returns 条件を満たす最小のl
     */
    minLeft(r: number, fn: (s: S) => boolean): number {
        // fn(e)がtrueであることを確認
        if (!fn(this.#e)) {
            throw new Error("fn(e) must be true");
        }
        // rの範囲チェック
        if (r < 0 || r > this.#originalSize) {
            throw new Error("r must be in range [0, size]");
        }
        // rが0と等しい場合、0を返す
        if (r === 0) {
            return 0;
        }
        // rに対応する葉ノードの位置を求める
        const right = r + this.#size;
        // 前処理: r側の遅延させていたタグは先にすべて計算しておく
        this.#pushToLeaves(r - 1, r);
        // モノイド積の初期値
        let product = this.#e;
        // minLeftの探索
        let pos = right;
        do {
            // 半開区間なので、とりあえず一個左に移動
            pos--;
            // 登れるだけ親に登る
            while (pos > 1 && (pos % 2 === 1)) {
                pos = Math.floor(pos / 2);
            }
            // 今のブロック(tree[pos])を足すと条件を満たさなくなるかチェックする
            if (!fn(this.#op(this.#data[pos], product))) {
                // 満たさない -> 境界はこのブロックの範囲 -> 葉まで降りていく
                while (pos < this.#size) {
                    // 遅延タグを伝播させる
                    this.#push(pos);
                    // 右の子に降りる
                    pos = pos * 2 + 1;
                    // 右の子なら結合しても大丈夫か?をチェック
                    if (fn(this.#op(this.#data[pos], product))) {
                        // 大丈夫なら結合して、左の子へ (さらに左を探る)
                        product = this.#op(this.#data[pos], product);
                        pos--; // 左の子へ
                    }
                }
                // 葉ノードに到達したら、そのindexを結果として返す
                return pos + 1 - this.#size;
            }
            // 今のブロックを足しても条件を満たすなら、足して次へ
            product = this.#op(this.#data[pos], product);
        } while ((pos & -pos) !== pos); // posが2冪になるまで繰り返す
        // すべて足しても条件を満たす場合、0を返す
        return 0;
    }
    /**
     * 全区間のモノイド積を返します。
     * @returns 全区間のモノイド積
     */
    queryAll(): S {
        return this.#data[1];
    }
    /**
     * 指定されたindexに対してfを作用させます。
     * @param index - 要素のインデックス (0-based)
     * @param f - 作用
     */
    applyAt(index: number, f: F): void {
        this.apply(index, index + 1, f);
    }
    /**
     * index番目の要素を返します。
     * @param index - 要素のインデックス (0-based)
     * @returns 指定されたインデックスの要素
     */
    get(index: number): S {
        return this.query(index, index + 1);
    }
    /**
     * index番目の要素をvalueに更新(上書き)します。
     * @param index - 要素のインデックス (0-based)
     * @param value - 新しい値
     */
    set(index: number, value: S): void {
        // indexに対応する葉ノードの位置を求める
        const leaf = index + this.#size;
        // 前処理: 上にある遅延を邪魔にならないように全部落とす
        this.#pushToLeaves(index, index + 1);
        // 値を上書き (遅延タグlazyはここには溜まっていないはずなのでdataだけでOK)
        this.#data[leaf] = value;
        // 後処理: 親を再計算
        this.#updateFromLeaves(index, index + 1);
    }
    /**
     * セグメント木のサイズを返します。
     * @returns セグメント木のサイズ
     */
    get size(): number {
        return this.#originalSize;
    }
}

function Main(inputText: string): void {
    const input: string[][] = inputText.trim().split("\n").map(row => row.split(" "));
    // Add your code here
    const [N, Q] = input[0].map(n => +n);
    const a = input[1].map(n => BigInt(n));
    const querys: ({kind: 0, l: number, r: number, b: bigint, c: bigint}|{kind: 1, l: number, r: number})[] = [];
    for (let i = 0; i < Q; i++) {
        if (+input[i + 2][0] === 0) {
            querys.push({
                kind: 0,
                l: +input[i + 2][1],
                r: +input[i + 2][2],
                b: BigInt(input[i + 2][3]),
                c: BigInt(input[i + 2][4])
            });
        } else {
            querys.push({
                kind: 1,
                l: +input[i + 2][1],
                r: +input[i + 2][2]
            });
        }
    }
    const MOD = 998244353n;
    // LazySegmentTreeを定義する
    const lazySegTree = new LazySegmentTree<{val: bigint, size: bigint}, {b: bigint, c: bigint}>(
        {val: 0n, size: 0n},
        (a, b) => ({ val: (a.val + b.val) % MOD, size: a.size + b.size }),
        (s, f) => ({val: (s.val * f.b + s.size * f.c) % MOD, size: s.size }),
        ({b: 1n, c: 0n}),
        (newF, oldF) => ({b: (oldF.b * newF.b) % MOD, c: (newF.b * oldF.c + newF.c) % MOD}),
        N,
        a.map(n => ({val: n, size: 1n}))
    );
    // クエリごと処理
    querys.forEach(query => {
        if (query.kind === 0) {
            const {l, r, b, c} = query;
            lazySegTree.apply(l, r, {b: BigInt(b), c: BigInt(c)});
        } else {
            const {l, r} = query;
            console.log(lazySegTree.query(l, r).val.toString());
        }
    });
}

export {}; // <- An empty export is required so that ts-check can determine it as a module.
Main(await Deno.readTextFile("/dev/stdin"));

提出情報

提出日時
問題 K - Range Affine Range Sum
ユーザ AXT_AyaKoto
言語 TypeScript 5.1 (Deno 1.35.1)
得点 0
コード長 18656 Byte
結果 TLE
実行時間 5552 ms
メモリ 443704 KiB

ジャッジ結果

セット名 Sample All
得点 / 配点 0 / 0 0 / 100
結果
AC × 1
AC × 13
TLE × 6
セット名 テストケース
Sample example_00
All example_00, max_random_00, max_random_01, max_random_02, random_00, random_01, random_02, small_00, small_01, small_02, small_03, small_04, small_05, small_06, small_07, small_08, small_09, small_random_00, small_random_01
ケース名 結果 実行時間 メモリ
example_00 AC 78 ms 44116 KiB
max_random_00 TLE 5552 ms 443552 KiB
max_random_01 TLE 5550 ms 443704 KiB
max_random_02 TLE 5548 ms 441420 KiB
random_00 TLE 5548 ms 405716 KiB
random_01 TLE 5547 ms 424416 KiB
random_02 TLE 5533 ms 238672 KiB
small_00 AC 69 ms 46936 KiB
small_01 AC 66 ms 46740 KiB
small_02 AC 70 ms 50360 KiB
small_03 AC 71 ms 49820 KiB
small_04 AC 75 ms 51704 KiB
small_05 AC 78 ms 51912 KiB
small_06 AC 76 ms 52140 KiB
small_07 AC 77 ms 51864 KiB
small_08 AC 68 ms 51704 KiB
small_09 AC 73 ms 52000 KiB
small_random_00 AC 78 ms 54516 KiB
small_random_01 AC 75 ms 53780 KiB