Y sequence HDU - 5297 十分优(lie)质的容斥

题目链接:HDU-5297

这道题有一个简化版:Eddy's爱好

如果做过简化版,看到这道题第一反应就是二分答案,找最小的N满足他前面的数(包含他自己)删去可以开1到r次方的数之后只剩下n个。但是这道题用二分答案会TLE(不知道写的好会不会,但是我会)。且找N他前面的数(包含他自己)删去可以开1到r次方的数之后剩下多少个的方法也很重要。

优(lie)质的找n他前面的数(包含他自己)删去可以开1到r次方的数之后剩下多少个的方法如下:(由于是蒟蒻,有错请大佬指正)

首先,二分的常数有点大,以下用了另一种和二分有点相似的枚举方式。先从假设答案为n,算出与标准答案差几个,加上去,直到与标准答案相等。

举个例子 n=70 r=6;

记S[i]为\sqrt[i]{x}<=n\sqrt[i]{x}为一个整数的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;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值