题目描述
话说PS总是有着各种各样的烦恼,这天,他又在为自己失败的感情史烦恼着。这时,他心中的女神,魔法少女小圆从天而降,她对他说,如果你能帮我解决一个问题,我就让你永远没有烦恼。
问题是这样的:
寻找一个最大的k,使得存在一个x使得x^k=y,那么f(y)=k,即y最多可以开k次方根。
小圆的要求是求出从a到b的f值之和(包括a和b)。
题目分析
这一道题有两种方法。
第一种:
首先,我们可以很自然地可以想到,对于a-b的答案,我们可以使用2-b的答案减去2-(a-1)的答案来得出。于是我们将问题第一次简化:
对于2-x,询问其答案是多少。 (当然,其实并没有简化多少)
可是,这样我们还是觉得很棘手(当然啦),于是我们需要寻找一下突破口。
于是,我便找到了:
这就是突破口!!!因为我们这样子就可以再一次简化题目:
对于2-x,询问其中
的数量是多少
然后,我们便可以通过转换来得出
的普遍情况。
然后,我们便可以得出一个容斥(应该是挺容易想到的)
我们设
然后结果就是
我们可以发现,对于
,它的系数是
于是,有没有发现,题目已经解决出来了呢?
注:在求
的时候,不要用pow函数(精度问题),要直接手打(是不是很坑?)
时间复杂度:
第二轮简化时要枚举
次,容斥需要
次,pow手打要
次(丑)。
于是最终的复杂度就是O(
)
第二种:
其实有一个更简单而又自然的方法。
我们发现,n虽然很大,大到连平方根都会超时,但在三次方根面前还是屈服了。
于是我们便可以想到一个高级解法——特殊处理!!!
对于一次方根和二次方跟的情况,我们直接加。
然后我们从1到 枚举,对于当前枚举到的x,我们知道可以累乘起来(次方),然后每累乘到一个数,我们就把答案加上相应的数,然后在这个数上打个标记,以防以后枚举到的时候又会累乘,然后就算重了——当然,对于大于 的数,我们就不用打标记了。
但还要注意减去与一次方根和二次方根重叠的情况,这里就不细说了。
时间复杂度:O( * ),但实际上远远比这要小(后面不会满 )。
代码(这里只有第一种)
#include<cstdio>
#include<cstring>
using namespace std;
typedef long long ll;
ll h[110];
ll pow(ll x,int y){
ll l=1,r=x,ans;
while(l<=r){
ll mid=(l+r)/2,d=mid;bool bk=true;
for(int i=2;i<=y;i++){
if(x/d<mid) {bk=false;break;}
d=d*mid;
}
if(bk) l=mid+1,ans=mid;
else r=mid-1;
}
return ans;
}
ll pd(int x){
ll sum=1;
for(int i=2;i<=x;i++){
if(x%i==0){
if((x/i)%i==0) return 0;
x=x/i;sum=-sum;
}
}
return sum;
}
ll solve(ll x){
ll sum=0;
for(int i=1;;i++){
ll t=pow(x,i);
if(t==1) break;
sum+=h[i]*(t-1);
}
return sum;
}
ll doit(ll x){
if(x==1) return 0;
ll sum=0;
for(int i=1;;i++){
ll t=pow(x,i);
if(t==1) break;
sum+=i*solve(t);
}
return sum;
}
int main()
{
memset(h,0,sizeof(h));
for(int i=1;i<=100;i++) h[i]=pd(i);
ll a,b;
while(scanf("%lld%lld",&a,&b)!=EOF){
if(a==0&&b==0) break;
printf("%lld\n",doit(b)-doit(a-1));
}
return 0;
}