見出し画像

AtCoder精選良問「B - Colorful Lines」Diff 576

この記事はAtCoderの精選良問集マガジンの中の記事です。
私が良問だと思った問題を解説付きでまとめています。

問題リンク

問題概要

H×Wのマス目が与えられ、その初期状態ではどのマスにも色は塗られていない。
その後、1からCまでのC種類の色が用意され、これらの色を使ってマス目に色を塗ることを考える。

色を塗る工程はQ個のクエリとして与えられ、各クエリは整数ti, ni, ciで構成される。
ここで、tiが1のときはni行目のマスを全て色ciで塗り、tiが2のときはni列目のマスを全て色ciで塗るという操作を行う。また、あるマスを色cで塗ると、そのマスの色は直前の状態に関係なく常に色cへと変化する。

色を塗る工程がすべて完了したときに、それぞれの色(1, 2, ..., C)で塗られているマスの数を求める。

考え方

このマガジンで初のARCの問題となる。
Diffは576となっているが、ARC補正みたいなものがあるのか?800まではいかないと思うが、感覚的には700くらいはあるように思った。面白い問題だと思ったので、紹介したい。

まずは入力例1をみてみる。

4 5 6 5
1 1 6
1 3 3
2 2 4
2 4 2
1 1 2

このクエリが与えられた際に、マスの色は下記のように変化をする。

.....   66666   66666   64666   64626   22222
.....   .....   .....   .4...   .4.2.   .4.2.
.....   .....   33333   34333   34323   34323
.....   .....   .....   .4...   .4.2.   .4.2.

愚直にシミュレートすれば答えを出すことができるが、HとWは10^9までとおおきいので、間違いなくTLEになる。

では、順番に各色ごとに何個あるか考えたらどうだろう。

愚直シミュレーションよりはましに思えるが、すこし考えると、これも厳しい。既に塗ってある色の上書きという動作があるので、既に塗ってある色をデクリメントしなければいけない。

こういう時は「後ろから考える」というテクニックを使う。
上の例で行くと、最後に1行目に2が塗られている。そのため、2は確実に1行目に塗られている。ここで1行目は既に使われていると、記録をする。

次に一個前の4列目に2を塗るという動作について。
1行目に既に2が塗られているため、行数  - 既に塗られている行数だけ、塗れる。

これを一番最初まで続けると答えになる。一般化すると、

行を塗るとき:

  • その行が使われていたら0

  • (列数 - 既に使われている列数)を塗る

列を塗るとき:

  • その列が既に使われていた0

  • (行数 - 既に使われている行数)を塗る

実装

import { createInterface } from "readline";
import * as fs from "fs";
import { log } from "console";
import { exit } from "process";

function main() {
    const [h, w, c, q] = nextBigInts(4)
    const queries: bigint[][] = []
    for (let i = 0n; i < q; i++) {
        const arr = nextBigInts(3)
        queries.push(arr)
    }
    // 使用した行
    const rowUsed: Set<bigint> = new Set()
    // 使用した列
    const columnUsed: Set<bigint> = new Set()
    // クエリをひっくり返す
    queries.reverse()
    // 答え
    const ans: bigint[] = new Array(Number(c + 1n)).fill(0n)
    for (const elm of queries) {
        const [t, n, color] = elm
        if (t == 1n) {
            // 使われたらスキップ
            if (rowUsed.has(n)) continue
            // 使ったことを記録
            rowUsed.add(n)
            // 列数 - 使われている列数を足す
            const used = BigInt(columnUsed.size)
            ans[Number(color)] += BigInt(w) - BigInt(used)
        }
        if (t == 2n) {
            if (columnUsed.has(n)) continue
            columnUsed.add(n)
            const used = BigInt(rowUsed.size) | 0n
            ans[Number(color)] += h - used
        }
    }
    // 一旦文字列に直す
    const stringArr = ans.map((value) => value.toString());
    // 出力
    log(stringArr.slice(1).join(' '))
}

let inputs = "";
let inputArray: string[];
let currentIndex = 0;

function next() {
    return inputArray[currentIndex++];
}
function nextNum() {
    return +next();
}
function nextBigInt() {
    return BigInt(next());
}
function nexts(length: number) {
    const arr = [];
    for (let i = 0; i < length; ++i) arr[i] = next();
    return arr;
}
function nextNums(length: number) {
    const arr = [];
    for (let i = 0; i < length; ++i) arr[i] = nextNum();
    return arr;
}
function nextBigInts(length: number) {
    const arr = [];
    for (let i = 0; i < length; ++i) arr[i] = nextBigInt();
    return arr;
}

// デバッグ環境がWindowsであれば条件分岐する
if (process.env.OS == "Windows_NT") {
    const stream = createInterface({
        input: process.stdin,
        output: process.stdout,
    });
    stream.on("line", (line) => {
        inputs += line;
        inputs += "\n";
    });
    stream.on("close", () => {
        inputArray = inputs.split(/\s/);
        main();
    });
} else {
    inputs = fs.readFileSync("/dev/stdin", "utf8");
    inputArray = inputs.split(/\s/);
    main();
}


この記事が気に入ったらサポートをしてみませんか?