koko的能量值公式 | ||||||
| ||||||
Description | ||||||
极之剑圣koko举起了胜利誓约之剑。 Koko面色凝重的盯着麦斯麦提克的大魔王AK_PUSH,喝到:“狂风,赐我予权柄!”。这是一门传说中的剑术,可以控制气流进行攻击。只见爆裂的气流散射状喷发出去,犹如一道道拉长了的白线,在空中划出无数美妙的弧线直逼大魔王AK_PUSH而去。。。。。。 咳咳。。小胖子koko最近在学幼稚园编程。。。感觉到单纯的学习很无聊,于是koko打算开发一款游戏,名叫幼稚园之勇者战魔王(咳咳)。 现在koko遇到了一个难题,他为游戏中剑圣职业开发了一个新技能“狂风权柄”,它的伤害计算公式如下: long long H( int n ) { long long res = 0; for( int i = 1; i <= n; i++ ) res = res + n / i; return res; } 其中n是使用的能量。由于按照上述公式直接计算伤害太慢了,所以koko又来请教聪明的你啦~你的任务是帮koko设计一个程序,可以比较高效的计算出“狂风权柄”的伤害值。
| ||||||
Input | ||||||
输入以T开始,代表测试样例的个数(T<=1000) 每组样例包括一个整数:n(n<2^31),代表能量值
| ||||||
Output | ||||||
每一组样例,输出H(N) | ||||||
Sample Input | ||||||
11 1 2 3 4 5 6 7 8 9 10 2147483647 | ||||||
Sample Output | ||||||
Case 1: 1 Case 2: 3 Case 3: 5 Case 4: 8 Case 5: 10 Case 6: 14 Case 7: 16 Case 8: 20 Case 9: 23 Case 10: 27 Case 11: 46475828386
| ||||||
Author | ||||||
小伙伴们@哈商大 |
思路:
1、考虑这样一个问题,对应n/i的每个单项的值:
我们发现,当随着i越来越大的时候,其单项的值的变化是-1的。
那么考虑什么时候开始才能使得随着i越来越大的时候,其单项的值的变化是-1的:
①我们在求一个数n是不是素数的时候,我们通常从1扫到sqrt(n);
②因为如果sqrt(n)以内有一个因子数zz,那么对应n/zz也是一个因子数,不需要继续判断了。
③那么考虑如果zz是一个因子数(zz<=sqrt(n)),那么对应n/zz和n/zz+1可能是差值大于1的,但是对应到n/n/zz和n/n/zz+1,其差值是一定小于等于1的。
④考虑到这里,大家不难想到,对应从1扫到sqrt(n)的单项(n/i)的相邻两个数的差值变化可能是大于1的,但是对应从sqrt(n)+1扫到n的单项(n/i)的相邻两个数的差值变化是一定小于等于1的。
2、那么我们最终可以这样来做这个题:
①前sqrt(n)个单项,我们直接累加起来。
②然后求出下一单项的值记做tmp;此时tmp不会很大(手动测试当n为2147483647的时候,tmp=46340),那么我们接下来对应区间内相邻两项差值为0的范围进行确定。那么我们此时二分查找最右边的等于tmp的位子,对应区间大小记做len,那么sum+=len*tmp;
③每一次二分结束之后,区间左端点的值=此时最右边的等于tmp的位子+1;tmp--;
④时间复杂度姑且可以算作:O(tmp*logn)(当然一定比这个值小);
Ac代码:
#include<stdio.h>
#include<string.h>
#include<math.h>
using namespace std;
#define ll long long int
int main()
{
int kase=0;
int t;
scanf("%d",&t);
while(t--)
{
ll n;
scanf("%lld",&n);
long long sum=0;
ll i;
for(i=1;i<=sqrt(n);i++)
{
sum+=n/i;
}
ll tmp=n/i;
while(tmp)
{
ll l=i;
ll r=n;
ll ans;
ll mid;
while(r-l>=0)
{
mid=(l+r)/2;
if(n/mid==tmp)
{
l=mid+1;
ans=mid;
}
else
{
r=mid-1;
}
}
sum+=tmp*(ans-i+1);
i=ans+1;
tmp--;
}
printf("Case %d: ",++kase);
printf("%lld\n",sum);
}
}