難易度評価

難易度を1〜10段階で評価できます。投票はあとから変更・取り消しできます。

難易度(投票) 未投票 (0票)

投票の分布

まだ投票はありません。

  1. Lv.10票
  2. Lv.20票
  3. Lv.30票
  4. Lv.40票
  5. Lv.50票
  6. Lv.60票
  7. Lv.70票
  8. Lv.80票
  9. Lv.90票
  10. Lv.100票

難易度の目安を見る ↗

ログインして難易度を投票

外部サイトへ移動します

リンク先の URL を確認してから開いてください。

別タブで開く

Blocks

作成者 amrit_overhail

難易度(作成者設定) Lv.4

難易度(投票) 未投票 (0票)

実行時間制限
3.5 秒
メモリ制限
512 MiB

解説

簡単のため、原点を (0,0,0)(0,0,0) とし、ブロックは x,y,z≥0x,y,z \ge 0 を満たすマス (x,y,z)(x,y,z) にのみ置くことができるとします。マス u=(x,y,z)u=(x,y,z) の原点からのマンハッタン距離を d(u)=x+y+zd(u)=x+y+z と表します。

原点からのマンハッタン距離が小さい順からブロックを置いていくことでできる三角錐の形状が最適になります。よって、できるだけ大きな三角錐を作り、端数をうまく処理することで正答を得ます。

なぜ三角錐の形状が最適かの説明

配置したブロックのうち、条件を満たしていない外側のマス A=(x,y,z)A=(x,y,z) に着目します。
マス AA のブロックを取り除いたときに条件を満たさなくなるマスは、x,y,zx,y,z の各方向について「 AA を最も外側のブロックとして依存していたマス」の高々 11 マスずつしか存在しないため、自身を除いて高々 33 マスです。

ブロックを最も効率よく使うには、外側にあるブロックの多くがこの上限である「 33 マス」を同時に支えている必要があります。

ここで、ブロック AA が 33 マス {X=(x′,y,z),Y=(x,y′,z),Z=(x,y,z′)X=(x',y,z),Y=(x,y',z),Z=(x,y,z') (x′<x,y′<y,z′<zx'<x,y'<y,z'<z)} を同時に支えているとします。各マスのマンハッタン距離を考えると、d(X)≤d(A)−1,d(Y)≤d(A)−1,d(Z)≤d(A)−1d(X) \le d(A)-1, d(Y) \le d(A) - 1, d(Z) \le d(A) - 1が成り立ちます。

隙間なく最も効率よく支えるとき、これら 33 マスは AA のすぐ手前(x′=x−1,y′=y−1,z′=z−1x' = x - 1, y' = y - 1, z' = z - 1)となり、d(X)=d(Y)=d(Z)=d(A)−1d(X) = d(Y) = d(Z) = d(A) - 1 とマンハッタン距離がすべて等しく一致します。

外側のブロックすべてにこの高効率な関係( 33 マスを同時に支える状態)を成り立たせるためには、条件を満たすマスの境界がどこでも同じマンハッタン距離で揃っていなければなりません。

したがって、原点からのマンハッタン距離が一定以下のマスをすべて満たすような三角錐の形状を考えるのが最適となります。

サイズ mm の三角錐に必要な総ブロック数は、F(x)=x(x+1)(x+2)6F(x)=\frac{x(x+1)(x+2)}{6} で求まります。 NN 個で構築可能な最大の三角錐のサイズ S3S_3 はこの関数をもとにオーバーフローに二分探索をすることで求まります。条件を満たすブロックは F(S3−1)F(S_3-1) で、残りのブロック数は N−F(S3)N-F(S_3) です。以降 N′N' とします。

三角錐の斜面を覆うように三角形状に配置します。サイズ xx の三角面を追加するのに必要なブロック数は G(x)=(x+1)(x+2)2G(x)=\frac{(x+1)(x+2)}{2} 個です。 N′N' 個で構築可能な最大の三角形のサイズ S2S_2 は、この関数をもとにオーバーフローに気をつけて二分探索をすることで求まります。 新たに条件を満たすブロックは G(S2−1)G(S_2-1) です。残りのブロック数は N′−G(S2)N'-G(S_2) です。以降 N′′N'' とします。

三角錐と三角面の間にできた溝に残りのブロック数をすべて配置します。端の1個のみ外壁が2つ必要であり、ほかは1個の外壁につき1個埋めることができます。よって新たに条件を満たすブロックは max⁡(0,N′′−1)\max(0, N''-1)個 であり、これを S1S_1 とします。

最終的な答えは S1+S2+S3S_1+S_2+S_3 になります。計算量は O(log⁡(X13))O(\log(X^\frac{1}{3})) であり、テストケース辺り十分高速に動作します。

実装例(C++)
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
template<typename T, typename F>
T bs(T ok, T ng, F is_ok){
    while(abs(ok-ng)>1){
        T mid = midpoint(ok, ng);
        if(is_ok(mid)){
            ok = mid;
        }else{
            ng = mid;
        }
    }
    return ok;
}
int main(){
    int t;
    cin>>t;
    auto f = [](ll v)->__int128_t{
        __int128_t x = v;
        return x*(x+1)*(x+2)/6;
    };
    auto g = [](ll v)->__int128_t{
        __int128_t x = v;
        return (x+1)*(x+2)/2;
    };
    while (t--){
        ll n;
        cin >> n;
        auto is_f = [&](ll x){return f(x)<=(__int128_t)n;};
        ll s3 = bs<ll>(0, 1LL<<22LL, is_f);
        n -= (ll)f(s3);
        auto is_g = [&](ll x){return g(x)<=(__int128_t)n;};
        ll s2 = bs<ll>(0, 1LL<<32LL, is_g);
        ll ans = (ll)f(s3-1)+(ll)g(s2-1)+max(0LL, n-(ll)g(s2)-1);
        cout<<ans<<'\n';
    }
}
実装例(Python)
def bs(ok, ng, is_ok):
    while abs(ok-ng)>1:
        m=(ok+ng)//2
        ok, ng=(m, ng) if is_ok(m) else (ok, m)
    return ok
t=int(input())
n_list=[int(input()) for _ in range(t)]
f=lambda x: x*(x+1)*(x+2)//6
g=lambda x: (x+1)*(x+2)//2
for n in n_list:
    s3=bs(0, 1<<30, lambda x: f(x)<=n)
    n-=f(s3)
    s2=bs(0, 1<<30, lambda x: g(x)<=n)
    print(f(s3-1)+g(s2-1)+max(0, n-g(s2)-1))
実装例(Rust)
use proconio::input;
fn bs<F: Fn(u64) -> bool>(mut ok: u64, mut ng: u64, is_ok: F) -> u64{
    while ok.max(ng)-ok.min(ng)>1{
        let mid = if ng>ok{
            ok+(ng-ok)/2
        }else{
            ok-(ok-ng)/2
        };
        (ok, ng) = if is_ok(mid){(mid, ng)}else{(ok, mid)};
    }
    ok
}
fn main(){
    input!{t: usize, n_vec: [u64; t]}
    let f = |x: u64|x.checked_mul(x+1).and_then(|a|a.checked_mul(x+2)).map(|a|a/6).unwrap_or(u64::MAX);
    let g = |x: u64|(x+1).checked_mul(x+2).map(|a|a/2).unwrap_or(u64::MAX);
    for n in n_vec{
        let mut ans = 0;
        let s3=bs(0, u64::MAX, |x|f(x)<=n);
        ans += f(s3-1);
        let n = n-f(s3);
        let s2=bs(0, u64::MAX, |x|g(x)<=n);
        ans += g(s2-1)+(n-g(s2)).saturating_sub(1);
        println!("{}", ans);
    }
}

ユーザー解説

ユーザー解説を書く

ユーザー解説はまだありません。