题目链接:HDU-5297
这道题有一个简化版:Eddy's爱好
如果做过简化版,看到这道题第一反应就是二分答案,找最小的N满足他前面的数(包含他自己)删去可以开1到r次方的数之后只剩下n个。但是这道题用二分答案会TLE(不知道写的好会不会,但是我会)。且找N他前面的数(包含他自己)删去可以开1到r次方的数之后剩下多少个的方法也很重要。
优(lie)质的找n他前面的数(包含他自己)删去可以开1到r次方的数之后剩下多少个的方法如下:(由于是蒟蒻,有错请大佬指正)
首先,二分的常数有点大,以下用了另一种和二分有点相似的枚举方式。先从假设答案为n,算出与标准答案差几个,加上去,直到与标准答案相等。
举个例子 n=70 r=6;
记S[i]为且为一个整数的x的集合
故S[2]={1,4,9,16,25,36,49,64}
S[3]={1,8,27,64}
S[5]={1,32}
S[6]={1,64}
ans的补集={1,4,8,9,16,25,27,32,36,49,64}
一共11个(即为1+cnt[S[2]]-1+cnt[S[3]-1+cnt[S[5]]-1-(cnt[S[6]]-1))注意这里是在计算ans的补集。
cnt(ans)=N-cnt[ans的补集];
具体流程讲解:
由于1十分特殊,故在容斥时将1排除在外。最后加上就可以了。
求cnt[S[i]]可以用pow函数 如cnt[S[i]]=pow(1.0*x+0.5,1.0/i)-1。(+0.5是为了精度,-1是筛掉1)
先删去所有小于等于N的质数次方数。那么64会重复,为什么呢,因为64=(2^3)^2=(2^2)^3,故会被算到两遍。(也就是S[2]与S[3]都包含S[6])
再加上有两种所有小于等于N的质数组成的数的次方数,减去有三种所有小于等于N的质数组成的数的次方数(注意此处组成是每种质数只有一个)。就和普通的容斥一样。
优(lie)质的就是在处理小于等于r的质数和小于等于63的有这些质数组成的数。(long long 的上限2^64-1)
首先处理质数要都取他的相反值。
开始队列为空,之后每放入一个质数都和队列里的数乘一下(该层新加如的数除外),所得数加入队列。
这个过程和DP有点相似。(处理出的数有正有负,正的说明是有偶数个质数组成,负的说明是奇数个质数组成)。再容斥即可。
AC代码:
#include<cstdio>
#include<cmath>
int r;
bool mark[105];
int id,prime[105],num[105],nnum;
void Init(){//筛出67以内的质数
for(int i=2;i<=67;i++){
if(mark[i])continue;
prime[++id]=-i;//质数直接筛成负数,在后面不用判个数
for(int j=i+i;j<=67;j+=i)mark[j]=1;
}
}
void change(){
nnum=0;
for(int i=1;-prime[i]<=r;i++){
int cnt=nnum;
for(int j=1;j<=cnt;j++)if(abs(num[j]*prime[i])<=63)num[++nnum]=num[j]*prime[i];//注意边界
//类似DP的处理出63以内的质数以及由质数组成的数(每个由参与组成的每种质数个数不超过一个)
num[++nnum]=prime[i];
}
}
long long get_ans(long long x){
long long ans=x;
for(int i=1;i<=nnum;i++){
long long tmp=1ll*pow(1.0*x+0.5,1.0/(1.0*abs(num[i])))-1;//x加0.5是为了精度, tmp等于有多少个数的num[i]次方小于等于x,减1是为了排除1
if(num[i]<0)ans-=tmp;//由奇数个质数组成
else ans+=tmp;//由偶数个质数构成
}
return ans-1;//1可以被开任意次方
}
void solve(long long n,int r){
change();
long long ans=n;
while(1) {//神奇的枚举方式
long long res=get_ans(ans);
if(res==n)break;
ans+=n-res;
}
printf("%lld\n",ans);
}
int main(){
int T;
Init();
scanf("%d",&T);
while(T--){
long long n;
scanf("%lld%d",&n,&r);
solve(n,r);
}
return 0;
}