解説
簡単のため、原点を とし、ブロックは を満たすマス にのみ置くことができるとします。マス の原点からのマンハッタン距離を と表します。
原点からのマンハッタン距離が小さい順からブロックを置いていくことでできる三角錐の形状が最適になります。よって、できるだけ大きな三角錐を作り、端数をうまく処理することで正答を得ます。
なぜ三角錐の形状が最適かの説明
配置したブロックのうち、条件を満たしていない外側のマス に着目します。
マス のブロックを取り除いたときに条件を満たさなくなるマスは、 の各方向について「 を最も外側のブロックとして依存していたマス」の高々 マスずつしか存在しないため、自身を除いて高々 マスです。
ブロックを最も効率よく使うには、外側にあるブロックの多くがこの上限である「 マス」を同時に支えている必要があります。
ここで、ブロック が マス { ()} を同時に支えているとします。各マスのマンハッタン距離を考えると、が成り立ちます。
隙間なく最も効率よく支えるとき、これら マスは のすぐ手前()となり、 とマンハッタン距離がすべて等しく一致します。
外側のブロックすべてにこの高効率な関係( マスを同時に支える状態)を成り立たせるためには、条件を満たすマスの境界がどこでも同じマンハッタン距離で揃っていなければなりません。
したがって、原点からのマンハッタン距離が一定以下のマスをすべて満たすような三角錐の形状を考えるのが最適となります。
サイズ の三角錐に必要な総ブロック数は、 で求まります。 個で構築可能な最大の三角錐のサイズ はこの関数をもとにオーバーフローに二分探索をすることで求まります。条件を満たすブロックは で、残りのブロック数は です。以降 とします。
三角錐の斜面を覆うように三角形状に配置します。サイズ の三角面を追加するのに必要なブロック数は 個です。 個で構築可能な最大の三角形のサイズ は、この関数をもとにオーバーフローに気をつけて二分探索をすることで求まります。 新たに条件を満たすブロックは です。残りのブロック数は です。以降 とします。
三角錐と三角面の間にできた溝に残りのブロック数をすべて配置します。端の1個のみ外壁が2つ必要であり、ほかは1個の外壁につき1個埋めることができます。よって新たに条件を満たすブロックは 個 であり、これを とします。
最終的な答えは になります。計算量は であり、テストケース辺り十分高速に動作します。
実装例(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);
}
}
ユーザー解説
ユーザー解説を書くユーザー解説はまだありません。