AtCoder题解 —— AtCoder Regular Contest 109 —— B - log —— 二分

本文围绕AtCoder Regular Contest 109 B题展开,介绍题目,Snuke买木头求最小代价。题解分析指出是数学题,可用二分查找,因数据范围大,起初考虑用__int128_t,后换思路从0 - 2e9找x,用long long即可,还给出AC代码及复杂度分析。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目相关

题目链接

AtCoder Regular Contest 109 B 题,https://atcoder.jp/contests/arc109/tasks/arc109_b

Problem Statement

Snuke is visiting a shop in Tokyo called 109 to buy some logs. He wants n logs: one of length 1, one of length 2, ..., and one of length n. The shop has n+1 logs in stock: one of length 1, one of length 2, …, and one of length n+1. Each of these logs is sold for 1

yen (the currency of Japan).

He can cut these logs as many times as he wants after buying them. That is, he can get k logs of length L1,…,Lk from a log of length L, where L=L_1+...+L_k. He can also throw away unwanted logs.

Snuke wants to spend as little money as possible to get the logs he wants. Find the minimum amount of money needed to get n logs of length 1 to n.

Input

Input is given from Standard Input in the following format:

n

Output

Print the minimum amount of money needed to get n logs of length 1 to n.

Samples1

Sample Input 1

4

Sample Output 1

3

Explaination

One way to get the logs he wants with 3 yen is:

  • Buy logs of length 2, 4, and 5.
  • Cut the log of length 5 into two logs of length 1 each and a log of length 3.
  • Throw away one of the logs of length 1.

Samples2

Sample Input 2

109109109109109109

Sample Output 2

109109108641970782

Constraints

  • 1 \leq n \leq 10^{18}

题解报告

题目翻译

Snuke 到东京的 109 商店去买一些木头。他需要买 n 个木头:一个长度为 1,一个长度为 2,...,和一个长度为 n。商店里有 n+1 个木头:1个长度为 1,一个长度为 2,...,和一个长度为 n+1。每个木头都卖 1 日元,不管长短。Snuke 可以将买到的木头切割成为他需要的长度,这样他就可以获得 k 个木头,例如长度为 L 的木头,可以切割为长度为 L_1, ..., L_k,也就是 L=L_1+...+L_k,他还可以任意丢弃木头。

求 Snuke 可以使用的最小代价达到他的要求是什么。

题目分析

看到求结果,就知道这是一个数学题。

根据题目,我们知道 Snuke 需要获得 1,2,...,N 长度的木头每个一跟,因此总木头长度为 \frac{n*(n+1)}{2}。而商店提供长度为 1,2,...,N,N+1 的木头每个一跟,每个木头的价格都是 1 日元,因此我们只需要找到最大 x,使得数列 x,x+1,...,n,n+1 的总和

x+(x+1)+...+n+(n+1)=\frac{(n+1+x)*(n+1-x+1)}{2}

大于等于 Snuke 需要的数列总和

\frac{n*(n+1)}{2}

即可。这样,我们可以使用二分查找找到这个最大的 X。这样我们需要的实际木头数量为 n+1-x+1。

既然确定可以使用二分查找,我们还需要确定使用二分模板,我们可以非常容易得到,这是一个二分查找带左端点的模板。二分查找模板可以查看我老的 Blog,https://blog.youkuaiyun.com/justidle/article/details/104527596?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522160674621019724848159656%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=160674621019724848159656&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~blog~first_rank_v1~rank_blog_v1-13-104527596.pc_v1_rank_blog_v1&utm_term=%E4%BA%8C%E5%88%86&spm=1018.2118.3001.4450

数据分析

根据题意,n 的最大值为 1e18,因此 n*(n+1) 的最大值为 1e18*1e18=1e36。这样就尴尬了,g++ 自带最大数据为 unsigned long long 也只能表示 1e19 的数据。好在 AtCoder 提供了最大的数据为 __int128_t,也就是 128b,比 long long 的 64b 大了一倍。

AC 参考代码

//https://atcoder.jp/contests/arc109/tasks/arc109_b
//B - log
#include <bits/stdc++.h>

using namespace std;

//如果提交到OJ,不要定义 __LOCAL
//#define __LOCAL

typedef long long LL;

LL n;

bool check(LL x) {
    __int128_t sum1=((__int128_t)n)*(n+1);
    __int128_t sum2=(((__int128_t)n)+2-x)*(n+1+x);
    return sum2>=sum1;
}

int main() {
#ifndef __LOCAL
    //这部分代码需要提交到OJ,本地调试不使用
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
#endif
    cin>>n;

    LL left=0;
    LL right=n+1;
    LL mid;
    while (left<right) {
        mid = (left+right+1)/2;
        if (check(mid)) {
            left=mid;
        } else {
            right=mid-1;
        }
    }

    cout<<n+2-left<<"\n";

#ifdef __LOCAL
    //这部分代码不需要提交到OJ,本地调试使用
    system("pause");
#endif
    return 0;
}

时间复杂度

O(logN)。

空间复杂度

O(1)。

换一个思路二分

由于使用和来判断数据量超过了 long long 的范围,我们换一个思路来二分查找。商店可以提供的序列是 1,2,...,N,N+1,Snuke 需要的序列是 1,2,...,N,我们将这两个序列的总和做一个减法,可得

\frac{(n+1)*(n+2)}{2}-\frac{n*(n+1)}{2}\\=\frac{(n+1)*(n+2)-n*(n+1)}{2}=\frac{(n+1)*(n+2-n)}{2}=n+1

也就是说,假设最大的 X,使得 Snuke 需要的序列为 x+1, x+2, ..., N, N+1 满足要求。哪么多余的序列为 1,2,...,X,也就是说这个 X 满足

\frac{x*(x+1)}{2}\leq n+1

根据题目给出的数据 n 的最大值为 1e18。当 n 越来越大,x 越趋近 0。因此我们可以考虑从 0 ~ 2e9 之间来查找 x。这样 2e9*2e9=4e18,而 long long 的大小为 9e18,这样使用 long long 就可以满足要求了,不需要使用 128b 的数据。

这样我们找到了 x,对应的答案就是 n-1+x。

特么都是满满的数学,想了我一个晚上,数学还是太烂。这不就是所谓的反问题,我真是个🐷。

简化 AC 代码

//https://atcoder.jp/contests/arc109/tasks/arc109_b
//B - log
#include <bits/stdc++.h>

using namespace std;

//如果提交到OJ,不要定义 __LOCAL
//#define __LOCAL

typedef long long LL;

LL n;

bool check(LL x) {
    LL sum1=x*(x+1)/2;
    LL sum2=n+1;
    return sum2>=sum1;
}

int main() {
#ifndef __LOCAL
    //这部分代码需要提交到OJ,本地调试不使用
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
#endif
    cin>>n;

    LL left=0;
    LL right=2e9;
    LL mid;
    while (left<right) {
        mid = (left+right+1)/2;
        if (check(mid)) {
            left=mid;
        } else {
            right=mid-1;
        }
    }

    cout<<n+1-left<<"\n";

#ifdef __LOCAL
    //这部分代码不需要提交到OJ,本地调试使用
    system("pause");
#endif
    return 0;
}

还好,时间有所提升,因为计算量小了。时间复杂度和空间复杂度之类,没有任何变化。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

努力的老周

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值