题解:[春季测试 2023] 幂次

题解:[春季测试 2023] 幂次

给定 \(n,k\) ,求有多少个整数 \(i \in [1,n]\) ,满足 \(i=a^b (a,b \in N^+,b \geq k)\)

算法一

\(k \ge 3:\) 发现只需要筛到1e6就没有贡献了,加上 \(set\) 暴力判重即可。

\(k=2:\) 发现有 \(\sqrt{n}\) 个完全平方数,考虑如何避免算重它们。考虑完全平方数的性质。发现有且既有下面两种数是完全平方数:

  1. 一个完全平方数的任意次幂。
  2. 任意一个数的偶次幂。

还是用 \(set\) 判重,加上如上两点判断即可。

最坏时间复杂度为 \(O(10^6 \times log_2^{10^6})\),注意开平方根要用sqrtl或者手写二分

#include<bits/stdc++.h>
#define F(i,l,r) for(ll i=(l);i<=(r);++i)
#define G(i,r,l) for(ll i=(r);i>=(l);--i)
#define ll long long
using namespace std;
set<ll> s;
ll n,k,x,cf;
//inline ll sqrt(ll x){
//	ll l=0,r=1000000010ll,mid;
//	while(l+1<r){
//		mid=(l+r)>>1;
//		if(mid*mid>x) r=mid;
//		else l=mid;
//	} return l;
int main(){
	scanf("%lld %lld",&n,&k);
	if(k==1) return printf("%lld",n),0;
	else if(k>2){
		F(i,2,n){
			x=i*i*i; if(x>n) break;	
			F(j,0,60){
				if(j+3>=k) s.emplace(x);
				if(x>n/i) break;
				x*=i;
			}
		}
		printf("%lld",s.size()+1);
	}
	else{
		ll ans=__builtin_sqrt(n);//要么用sqrtl,要么手写一个longlong的开根(最后一组数据卡精度)
		F(i,2,n){
			if(i*i*i>n) break;
			ll tmp=__builtin_sqrt(i); if(tmp*tmp==i) continue;
			x=i*i,cf=2;
			do{
				++cf,x*=i;
				if((cf&1) && s.find(x)==s.end()) s.emplace(x);
			}while(x<=n/i);
		}
		ans+=s.size();
		printf("%lld",ans);
	}
	return 0;
}

算法二

我们考虑换一种判重方法:

1.对于 \(k \ge 3\) , 若 \(a^x=b^y\), 则有 \(a^x=(a^k)^y\), 那么在以 \(a\) 为底数时就一定能把 以 \(b\) 为底的情况给筛掉,所以我们记 \(vis_i\) 表示 底数 \(i\) 有没有被筛掉,然后就可以愉快地筛起来了。

vis只需要开到 1e6 .

2.对于 \(k=2\), 很明显开不下 vis[1e9], 所以再单独特判一下每个数是不是完全平方数即可。最后加 \(\sqrt{n}\) 个完全平方数,并减去重复的。

时间复杂度近似 \(\sqrt{n}.\)

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e6+2;
int n,k,K,m,same=0,sq,ans=0;
bitset<N> b;
int qp(int x,int y){
	int res=1;
	while(y){ if(y&1){ if(res>n/x) return n+1; res=res*x; }y>>=1,x*=x; }
	return res;
}
signed main(){
	ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
	cin>>n>>K; if(K==1) return cout<<n,0;
	sq=__builtin_sqrtl(n),k=max(K,3ll);
	int l=0,r=1e6+2,mid;
	while(l+1<r) mid=(l+r)>>1, (qp(mid,k)<=n) ? l=mid : r=mid;
	for(int i=2;i<=l;++i){
		if(b[i]) continue;
		int z=i,o=0;
		while(z<=n){
			o++; 
			if(z<=l) b[z]=1;//标记 
			if(o>=k) ans++;//累计答案 
			if((int)sqrt((int)z)*(int)sqrt((int)z)==(int)z&&o>2) same++;//容斥(去重) 
			if(z>n/i) break;//判超界 
			z*=i;
		}
	}
	if(K==2) ans=ans+sq-1-same;
	cout<<ans+1;
	return 0;
}
posted @ 2023-11-12 20:15  superl61  阅读(40)  评论(0编辑  收藏  举报