5163. 【NOIP2017模拟6.25】PS的烦恼

题目描述

话说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 f ( i ) = y z 当2-x中f(i)=y的个数为z时
2 x y f ( i ) = 1 z 则2-\sqrt[y]{x}中f(i)=1的个数也为z

这就是突破口!!!因为我们这样子就可以再一次简化题目:
对于2-x,询问其中 f ( i ) = 1 f(i)=1 的数量是多少
然后,我们便可以通过转换来得出 f [ i ] = z f[i]=z 的普遍情况。

然后,我们便可以得出一个容斥(应该是挺容易想到的)

我们设 g [ i ] = j = 1 l o g ( n ) / i f [ i j ] g[i]=\sum_{j=1}^{log(n)/i}{f[i*j]}
然后结果就是
+ g [ 1 ] g [ 2 ] g [ 3 ] g [ 5 ] + g [ 6 ] g [ 7 ] + g [ 10 ] . . . . . . +g[1]-g[2]-g[3]-g[5]+g[6]-g[7]+g[10]......
我们可以发现,对于 g [ i ] g[i] ,它的系数是 μ ( i ) \mu(i)
于是,有没有发现,题目已经解决出来了呢?

注:在求 x y \sqrt[y]{x} 的时候,不要用pow函数(精度问题),要直接手打(是不是很坑?)
时间复杂度:
第二轮简化时要枚举 l o g 2 n log_2n 次,容斥需要 l o g 2 n log_2n 次,pow手打要 l o g 2 2 n log_2^2n 次(丑)。
于是最终的复杂度就是O( l o g 2 4 n log_2^4n )

第二种:

其实有一个更简单而又自然的方法。
我们发现,n虽然很大,大到连平方根都会超时,但在三次方根面前还是屈服了。
于是我们便可以想到一个高级解法——特殊处理!!!

对于一次方根和二次方跟的情况,我们直接加。

然后我们从1到 n 3 \sqrt[3]{n} 枚举,对于当前枚举到的x,我们知道可以累乘起来(次方),然后每累乘到一个数,我们就把答案加上相应的数,然后在这个数上打个标记,以防以后枚举到的时候又会累乘,然后就算重了——当然,对于大于 n 3 \sqrt[3]{n} 的数,我们就不用打标记了。

但还要注意减去与一次方根和二次方根重叠的情况,这里就不细说了。

时间复杂度:O( n 3 \sqrt[3]{n} * l o g 2 n log_2n ),但实际上远远比这要小(后面不会满 l o g 2 n log_2n )。

代码(这里只有第一种)

#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;
}
发布了58 篇原创文章 · 获赞 12 · 访问量 8573

猜你喜欢

转载自blog.csdn.net/fengqiyuka/article/details/82940111