石油大 金币 二分答案

题目描述

乔治在梦中来到了一个神奇部落,这个部落的神树具有奇特的功能:对于每一位新朋友,都会获赠金币,而且金币的数量会随时间的延续而增加:
第1周,每天1枚金币;
第2周,每天2枚金币;
第3周,每天3枚金币;……
请问:至少多少天,乔治的金币数量达到n枚?

输入

一行,只有一个正整数n。

数据范围

对于30%的数据,n不超过2147483647;
对于100%的数据,n的位数不超过18。

输出

一行,一个整数,表示金币达到n枚所需的最少天数。

想法

数据量在1e19, 所以枚举1-1e19周?
我们发现, 利用等差数列求和估算一下, 最多为第1e10周
所以枚举 1-1e10周
暴力枚举是不行了, 所以采用二分答案的方式
先利用二分求出第m周, 又因为第m周每天增加m个金币
故可以利用这一特性, 枚举出哪一天
另要注意,数据量1e10, 开个(unsigned) long long, 仍要防止相乘溢出的情况

参考

二分查找模板参考Y总
关于由数据量估计算法复杂度和内容参考Y总

实现

#include <cstdio>
#include <iostream>
#include <cmath>
using namespace std;
typedef unsigned long long ull; // 18位的数据量
ull n, day, week;
bool check(ull week) // 形参一开始写成了int,  WA了好久, 长记性了
{
    // 等差数列求和公式化简后
    ull temp = 7*week*(week+1);
    if(temp >= n*2) return true;
    return false;
}
int binarySearch()
{
    ull left = 1, right = 1e10 + 9; // 不要傻乎乎的枚举1e19
    // .......voooooo(.代表不满足, o代表满足, 我们要的目标就是v  代表week)
    while(left < right) // 二分模板参考 Y总帖子: https://www.acwing.com/blog/content/31/
    {
        ull mid = (left + right) >> 1; // 
        if(check(mid)) right = mid; // mid是满足情况的(不能丢掉!!!), 所以按[left, mid][mid+1,right]划分, 答案在[left,mid]中
        else left = mid + 1; // mid不满足情况, 答案在[mid+1,right]中
    }
    return left;
}
ull solve()
{
    ull day = week*7;
    ull sum = 0;
    // 求一下week对应的day, 这里分情况, 主要是怕/2时,丢失精度
    if(week%2 == 0) sum = week*7 + week/2*(week-1)*7;
    else sum = week*7 + (week-1)/2*week*7;
    // 枚举出满足情况的最小天数
    while(sum >= n)
    {
        day--;
        sum -= week;
    }
    return day+1;
}
int main()
{
    cin >> n;
    week = binarySearch(); // 二分确定在哪一周
    day = solve(); // 枚举出哪一天, 最多枚举7次
    cout << day << endl; 
    system("pause");
    return 0;
}

posted @ 2020-12-06 10:46  a-shy-coder  阅读(356)  评论(0编辑  收藏  举报