@noi.ac - 171@ 立方体


@description@

TonyFang 打算送你一些立方体。

你需要在 [1, n] 中选择一个整数 k。在送你的立方体的体积和不超过 k 的情况下,TonyFang 会不断给你一个边长为正整数且尽可能大的立方体。

你需要求出最多能得到多少个立方体,以及在此条件下,k 的最小值和最大值。

input
一行一个整数 n。

output
三行,每行一个整数,分别表示最多立方体个数,容量最小值和容量最大值。

sample input
14
sample output
7
7
14

对于 100% 的数据,1≤n≤10^15。

@solution@

根据题目可以写出一个简单的 dp 求出每一个 k 能得到的立方体个数:dp[k] = dp[k-x^3] + 1。
其中 x 表示 k 的立方根下取整。

有一个小小的性质:当 x 足够大时,2*x^3 > (x+1)^3。通过函数增长速率可以得到这个结论。
这意味着当 x 足够大时,同一大小的立方体只会被赠予一次,否则就可以赠予更大的立方体。

考虑询问 1~n 的中的答案,我们可以将 1~n 分为两块:1~v^3-1 与 v^3~n,其中 v 是 n 的立方根下取整。
因为 n <= 10^15,v 的取值最多只有 10^5。现在假设我们可以通过某种手段预处理出 1~v^3-1 的答案,询问 v^3~n 的答案是多少。
由我们上面那个 dp,可以得到 dp[v^3+d] = dp[d] + 1,现在问题转变为求解 0~n-v^3 的答案,再通过一点小小的变化即可得到 v^3~n 的答案。
可以发现这是一个与原问题类似,只是规模更小的子问题。

这样递归,由我们推导的性质可得,每次问题的规模至少减为原规模一半,故只会递归 log 次。
同时为了保证上面的性质成立(即要求 x 足够大的条件成立),我们可以预先处理出 1~50^3 (50^3 = 1.25*10^5)中的所有答案,当规模在这个范围的时候可以直接得到答案。

再考虑怎么预处理 1~v^3 的答案。可以将它拆成 1~(v-1)^3-1 与 (v-1)^3~v^3,然后仿照上面的方法即可。

题解中采用的是贪心的做法,但我觉得可能这个方法(对我而言)要直观一些。

@accepted code@

#include<cmath>
#include<cstdio>
#include<algorithm>
using namespace std;
const int MAXN = 125000 - 1;
const int MAXM = 100000;
typedef long long ll;
ll dp[MAXN + 5], f1[MAXN + 5], f2[MAXN + 5], f3[MAXN + 5];
ll g1[MAXM + 5], g2[MAXM + 5], g3[MAXM + 5];
ll pow3(ll x) {return x*x*x;}
ll cub(ll x) {
    ll le = 1, ri = MAXM;
    while( le < ri ) {
        ll mid = (le + ri + 1) >> 1;
        if( pow3(mid) > x ) ri = mid - 1;
        else le = mid;
    }
    return le;
}
void update(ll a, ll b, ll c, ll &x, ll &y, ll &z) {
    if( a > x )
        x = a, y = b, z = c;
    else if( a == x )
        y = min(y, b), z = max(z, c);
}
void solve(ll x, ll ri, ll &a, ll &b, ll &c) {
    ll le = pow3(x);
    if( ri-le <= MAXN )
        a = f1[ri-le], b = f2[ri-le], c = f3[ri-le];
    else {
        ll p = cub(ri-le);
        solve(p, ri-le, a, b, c);
        update(g1[p-1], g2[p-1], g3[p-1], a, b, c);
    }
    a++, b += le, c += le;
}
void init() {
    int x = 1;
    for(int i=1;i<=MAXN;i++)
        dp[i] = dp[i-pow3(cub(i))] + 1;
    for(int i=1;i<=MAXN;i++) {
        f1[i] = dp[i], f2[i] = f3[i] = i;
        update(f1[i-1], f2[i-1], f3[i-1], f1[i], f2[i], f3[i]);
    }
    for(int i=1;i<MAXM;i++) {
        ll x = pow3(i+1);
        if( x-1 <= MAXN )
            g1[i] = f1[x-1], g2[i] = f2[x-1], g3[i] = f3[x-1];
        else {
            solve(i, x-1, g1[i], g2[i], g3[i]);
            update(g1[i-1], g2[i-1], g3[i-1], g1[i], g2[i], g3[i]);
        }
    }
}
int main() {
    ll n; init();
    scanf("%lld", &n);
    if( n <= MAXN ) {
        printf("%lld\n%lld\n%lld\n", f1[n], f2[n], f3[n]);
        return 0;
    }
    ll x = cub(n), res1, res2, res3;
    solve(x, n, res1, res2, res3);
    update(g1[x-1], g2[x-1], g3[x-1], res1, res2, res3);
    printf("%lld\n%lld\n%lld\n", res1, res2, res3);
}

@details@

好像 zxb 大佬用了打表+二分跑得飞快。。。总耗时 0ms。。。

感觉这道题乱搞的方法好多啊。
然而正解的贪心基本没人写23333

转载于:https://www.cnblogs.com/Tiw-Air-OAO/p/11115031.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值