题目要求
题目描述
乔治在梦中来到了一个神奇部落,这个部落的神树具有奇特的功能:对于每一位新朋友都会获赠金币,而且金币的数量会随时间的延续而增加:
第 1 周 | 每天 1 枚金币 |
第 2 周 | 每天 2 枚金币 |
第 3 周 | 每天 3 枚金币 |
…… | …… |
请问:至少多少天,乔治的金币数量达到 n 枚?
输入格式
一行,只有一个正整数 n。
输出格式
一行,一个整数,表示金币达到 n 枚所需的最少天数。
样例输入
30
样例输
17
提示/说明
【样例说明】
第 1 周:每天 1 枚,共 7 枚;
第 2 周:每天 2 枚,共 14 枚;
第 3 周:每天 3 枚,3 天即可:7+14+3*3=30。
共计:7+7+3 = 17 天。
【数据规模】
对于 30%的数据, n 不超过 2147483647;
对于 100%的数据, n 的位数不超过 18。
解题步骤
这道题属于中等难度,考验代码能力。首先分析一下问题,告诉我们每周可以得多少个金币,求需要多少天。首先想到的就是for循环,不断用i去试余数,用变量ans累加天数,直到余数为零或不够为止。这很简单,代码如下:
#include<bits/stdc++.h>
#define int long long //捣乱一下很好玩
using namespace std;
main(){
int n, ans = 0;
scanf("%lld", &n);
for(int i = 1; ; i ++){
if(n - 7 * i > 0)
n -= 7 * i, ans += 7; //以一个星期7天为单位,可以优化时间复杂度
else if(n - 7 * i <= 0) //如果不够直接退出
{
ans += n / i;
break;
}
}
printf("%lld", ans);
return 0;
}
但是这样的话显而易见:超时!
来简单计算一下,假设n顶到头,那么n=999,999,999,999,999,999,时间复杂度保守估计差不多是
左右,肯定是超了。那么还得另寻他法。
我们来找找金币的变化规律:
周 | ||||||
金币 |
总数 |
|
|
|
观察一下,金币的数量不就是等差数列吗?
根据等差数列求和公式 和=项数×(项数+1)÷2 ,我们可以知道每个周结束时我们有多少金币。
而且周数是上升的,金币数也是上升的,符合二分查找的单调性,这样就可以使用二分了。二分周数,求出每个周之前的金币数,查找正好比n小的值的下标(找最右值),即周数。如果有多出来的就减去整的,再向上取整,求出多出的天数,再加上原来的天数就行了。
鉴于我解释的比较生硬,我们举一个例子来说一下:
输入:45
那么开始二分下标,得到最右值是3,那么就用
7×3(天数)+(向上取整)ceil ( n- (r×(r+1)÷2)(数列求和公式)÷ r+1 (新的一周的金币数) )
AC代码
到了你们最喜欢的 话不多说上代码!
#include<bits/stdc++.h>
using namespace std;
typedef long long ll; //比较懒
//n用long long
// 二分周数,如果不正好的话算出超出的发了多少天,向上取整
// 等差数列求和公式:项数×(项数+1)÷2
ll kun(ll n)
{
return (7 * n * (n + 1) ) >> 1;
} //等差数列求和公式(请各位ikun自行忽略函数名)
ll cceil(ll x, ll y){ return x % y == 0 ? x / y : x / y + 1; } //自定义向上取整,因为普通ceil会范围超限
int main(){
ll n, ans = 0;
scanf("%lld", &n); //最好用scanf,不然可能TLE
ll l = 1, r = 1e9; //右指针最好设大一点
while(l <= r) { //二分模板
ll mid = (l + r) >> 1; //用右移运算,不然可能爆
if( kun ( mid ) <= n ) l = mid + 1;
else r = mid - 1;
}
printf( "%lld", 7*r + cceil( n - kun(r) , r+1 ) ); //个人习惯在括号比较多的时候加上空格逻辑清晰
return 0;
}
一道有点点难度的二分题,希望大家能有所收获,掰掰ヾ(•ω•`)o
本文可以转载,请注明作者,谢谢