公式

D - 三角形ボードの最適経路 / Optimal Path on a Triangular Board 解説 by MMNMM


まず、非負整数 \(x\) に対して \(f(x)\) の値を求めることを考えます。

これは \(x\) の桁数を \(d\) とすると、\(S\left(\left\lfloor\dfrac{x+1}{10 ^ {d-1}}\right\rfloor\times10 ^ {d-1}-1\right)\) となることが示せます(\(\left\lfloor\dfrac{x+1}{10 ^ {d-1}}\right\rfloor\times10 ^ {d-1}-1\) は、最上位以外の桁がすべて \(9\) であるような整数のうち、\(x\) を超えない最大のものです)。

これを用いて計算した \(f(A _ {i,j})\) を改めて \(A _ {i,j}\) とおくと、(\(L\lt P\) となる問い合わせを取り除いた上で)解くべき問題は次のようになります。

\(L\le i\lt L+T,P\le j\le P+i-L\) を満たす \((i,j)\ (1\le i\le N)\) にわたる \(A _ {i,j}\) の最大値を求めよ。

これは、Sparse Table のような構造を用いることでクエリあたり \(3\) つの値の最大値を求めることで解くことができます。

具体的には、\(t _ 1=1,t _ {i+1}=t _ {i}+\lceil t _ i/2\rceil\) について、すべてのマス \((l,p)\) に対する問い合わせ \((l,p,t _ i)\) の結果を \((l,p,t _ {i-1})\) から求めることができます。

前計算の時間計算量および空間計算量は \(O(N ^ 2\log N)\) 、クエリあたりの計算量は \(O(\log N)\) などとできます。

この問題では \(NQ\) に対する制約があるため、クエリあたり \(O(N)\) 時間などをかける解法であっても十分高速です。

実装例は以下のようになります。

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

int main() {
    int N, Q;
    cin >> N >> Q;

    vector<vector<int>> A(N);
    for (int i = 0; i < N; ++i) {
        A[i].resize(i + 1);
        for (int& a : A[i]) {
            // f(A[i][j]) を求める
            cin >> a;
            string S = to_string(a + 1);
            a = S[0] + size(S) * 9 - 58; // 越えない範囲で 9 を詰め込んだものが最大
        }
    }

    // 三角形領域に対する Sparse Table
    vector<vector<vector<int>>> doubling{A};
    for (int d = 1; d <= N; d += (d + 1) / 2) {
        int w = (d + 1) / 2;
        vector<vector<int>> next = doubling.back();
        for (int i = 0; i < N; ++i) {
            for (int j = 0; j <= i; ++j) {
                next[i][j] = max({doubling.back()[i][j], doubling.back()[min(i + w, N - 1)][j], doubling.back()[min(i + w, N - 1)][j + min(i + w, N - 1) - i]});
            }
        }
        doubling.emplace_back(next);
    }

    for (int i = 0; i < Q; ++i) {
        int L, P, T;
        cin >> L >> P >> T;
        --L;
        --P; // 0-indexed にしておく
        if (P > L) { // 外側なら NA
            cout << "NA" << endl;
            continue;
        }
        T = min(T + 1, N - L);
        int w = 1, level = 0;
        while (w + (w + 1) / 2 < T) {
            w += (w + 1) / 2;
            ++level;
        }
        // 前計算の結果を適切に取ることで O(1) 個の最大値が答えになるようにできる
        cout << max({doubling[level][L][P], doubling[level][L + T - w][P], doubling[level][L + T - w][P + T - w]}) << endl;
    }
    return 0;
}
N, Q = map(int, input().split())

# f(S) を求める
def f(S):
    S = str(int(S) + 1)
    return int(S[0]) - 10 + len(S) * 9 # 越えない範囲で 9 を詰め込んだものが最大

A = [list(map(f, input().split())) for i in range(N)]

# 三角形領域に対する Sparse Table
doubling = [A]
d = 1
while d <= N:
    w = (d + 1) // 2
    next = [[0 for i in r] for r in A]
    for i in range(N):
        for j in range(i + 1):
            next[i][j] = max(doubling[-1][i][j], doubling[-1][min(i + w, N - 1)][j], doubling[-1][min(i + w, N - 1)][j + min(i + w, N - 1) - i])
    doubling.append(next)
    d += w

for i in range(Q):
    L, P, T = map(int, input().split())
    L -= 1
    P -= 1 # 0-indexed にしておく
    if P > L: # 外側なら NA
        print('NA')
        continue
    T = min(T + 1, N - L)
    w = 1
    level = 0
    while w + (w + 1) // 2 < T:
        w += (w + 1) // 2
        level += 1
    # 前計算の結果を適切に取ることで O(1) 個の最大値が答えになるようにできる
    print(max(doubling[level][L][P], doubling[level][L + T - w][P], doubling[level][L + T - w][P + T - w]))

投稿日時:
最終更新: