石油大 金币 二分答案
题目描述
乔治在梦中来到了一个神奇部落,这个部落的神树具有奇特的功能:对于每一位新朋友,都会获赠金币,而且金币的数量会随时间的延续而增加:
第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, 仍要防止相乘溢出的情况
参考
实现
#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;
}