用容斥原理解决素数筛类似问题

首先用容斥原理时千万注意时间复杂度,建议不要用二进制去枚举子集,很容易超时,我们用dfs进行枚举比二进制更快,还有如果空间允许打表,就打表做,别用容斥

- 东北林业大学第13届校赛(老生组)D无平方因子

平方因子个数
Problem:D
Time Limit:1000ms
Memory Limit:65535K
Description

给出一个n,求1-n中因子不包含除1以外的平方数的数有多少个?

Input

输入T,T组数据 T<=1000
输入N<=1000,000,000

Output

输出个数

Sample Input

2
2
3

Sample Output

2
3

当时看到这题首先想到用刘汝佳书上的方法,用类似素数筛的方法去做,但是做不了,应为题目的数据限制,你开不了n(1e9)大小的数组,去计算前缀和。。。
然后在群里咨询了大佬,大佬告诉我筛选m=sqrt(n)内的素因子(k个),然后用容斥解,我琢磨用容斥枚举所有子集的时间是2^k会TLE啊。。。试一下然后tle了。。
后来发现是我容斥打开方式不对。。。。。。用一个dfs来枚举可以大大降低时间复杂度

比如说100,sqrt(100)的素因子为:2,3,5,7.i=0;i<3;i++
ret+=100/4-dfs(2,25) 25>9
ret+=100/4-(25/9-dfs(3,2)+25/25-dfs(4,1))(i=0,ret=22)//这个意思就是容斥
ret+ =100/9- dfs(3,11) (i=1,ret=33)
ret+=100/25-dfs(4,4) (i=2,ret=37)
ret+=100/49 (i=2,ret=39)

#include<iostream>
#include<cstdio>
#include<cstring>
#define N 100005
#define LL long long
using namespace std;

bool vis[N];
int pri[N],k=0;

void prime(){
	memset(vis,0,sizeof(vis));
	for(int i=2;i<N;i++){
		if(vis[i])continue;	
		pri[k++]=i;
		for(int j=i*i;j<N;j+=i)vis[j]=1;
	}
} 

int dfs(int id,int n){
	int ret=0;
	for(int i=id;i<k&&pri[i]*pri[i]<=n;i++)
		ret+=n/pri[i]/pri[i]-dfs(i+1,n/pri[i]/pri[i]);//这种枚举方法使时间够快 
	return ret;	
}

int main(){
	prime();
	int t,n;
	scanf("%d",&t);
	while(t--){
		scanf("%d",&n);	
		printf("%d\n",n-dfs(0,n));
	}
}
  • Lightoj(1179)

这题的解法是用区间筛做,演示一下容斥的超时写法,所以用容斥的时候注意时间复杂度

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
using namespace std;
typedef long long ll;
//用容斥请注意时间复杂度 
const int N=1e5+100;
int p[N/10],k=0;
bool vis[N];
void prime(){
	memset(vis,0,sizeof(vis));	
	ll m=(ll)sqrt(N+0.5);
	for(int i=2;i<=m;i++)if(!vis[i])
		for(int j=i*i;j<N;j+=i)vis[j]=1;
	
	for(int i=2;i<N;i++)if(!vis[i])p[k++]=i;
}

int dfs(int id,int n){
	int ret=0;
	for(int i=id;p[i]<=n&&i<k;i++)
		ret+=n/p[i]-dfs(i+1,n/p[i]);
	return ret;
}
int cnt(int x){//这个用于去掉被计算成1倍的素数 
	int i;
	for(i=0;i<k;i++)if(p[i]>x)break;
	return i-1;
}

int main(){
	prime();
	int t,kas=0,a,b,ans;
	scanf("%d",&t);
	while(t--){
		scanf("%d%d",&a,&b);
		a--;
		int na=a-dfs(0,a)+cnt(a);//a-1以内的素数个数 
		int nb=b-dfs(0,b)+cnt(b);//b以内的素数个数 
		ans=nb-na;
		printf("Case %d: %d\n",++kas,ans);	
	}
}

素数筛做法

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
using namespace std;
typedef long long ll;
//用容斥请注意时间复杂度 
const int N=1e6+100;
int p[N],k=0;
bool vis[N];
void prime(){
	memset(vis,0,sizeof(vis));	
	ll m=(ll)sqrt(N+0.5);
	for(int i=2;i<=m;i++)if(!vis[i])
		for(int j=i*i;j<N;j+=i)vis[j]=1;	
	for(int i=2;i<N;i++)if(!vis[i])p[k++]=i;
}



int main(){
	prime();
	bool flag[N];
	int t,kas=0,a,b,ans;
	scanf("%d",&t);
	while(t--){
		int ans=0; 
		scanf("%d%d",&a,&b);
		if(a==1)ans--;
		int n=b-a+1;
		memset(flag,0,sizeof(bool)*(b-a+10));
		for(int i=0;i<k&&p[i]*p[i]<=b;i++){
			int j=0;
			if(a%p[i]!=0)j=j-a%p[i]+p[i];
			if(a<=p[i])j+=p[i];
			for(;j<n;j+=p[i])flag[j]=1;	
		}
		for(int i=0;i<n;i++)if(!flag[i])ans++; 
		printf("Case %d: %d\n",++kas,ans);	
	}
}

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值