容斥原理+dfs剪枝 - ost数(WOJ 2592)

cost数

描述
“给你一个有n个正整数的数列{an}。一个正整数x若满足在数列{an}中存在一个正整数ai,使x≡17(mod ai),那么x就是一个‘cost数’。请问1到m的正整数中,有多少个‘cost数’?”
输入
第一行两个正整数n和m,意义见问题描述。
第二行n个正整数,分别为数列{an}中的n个数。

输出
输出一个整数,表示1到m中“cost数”的个数。

样例输入
3 100
18 22 23
样例输出
11

【数据范围】
对于20%的数据,有n≤20,m≤100,000;
另有40%的数据,n≤3,m<2^31;
对于100%的数据,有n≤30,m<231,17<ai<231
(注:“≡”为同余符号,a≡b (mod k) 即a mod k = b mod k)


Analysis

考场上推了半天,倒是想出了容斥……奇加偶减 n &lt; = 3 n&lt;=3 n<=3很好做啊,然后很多很多的怎么弄啊
后来看题解发现就是dfs看每个数选还是不选,如果这次选出来的数个数为奇,就 a n s + = ( m − 17 ) / l c m ans+=(m-17)/lcm ans+=(m17)/lcm,否则就减
最后再把 a n s + 1 ans+1 ans+1(17没有被算)就可以得到答案了
再顺手剪剪枝,比如:
当前 l c m &gt; m lcm&gt;m lcm>m时就不用继续了

还有些细节要注意一下:
比如这样写 a n s + = m / l c m , a n s − = ( n − 1 ) ans+=m/lcm,ans-=(n-1) ans+=m/lcm,ans=(n1)是错误的
具体原因就是因为我们剪了枝,会导致多算的17没有被减或者减多了


Code
#include<bits/stdc++.h>
#define in read()
#define ll long long
using namespace std;
inline int read(){
	char ch;int f=1,res=0;
	while((ch=getchar())<'0'||ch>'9') if(ch=='-') f=-1;
	while(ch>='0'&&ch<='9'){
		res=(res<<3)+(res<<1)+ch-'0';
		ch=getchar();
	}
	return f==1?res:-res;
}
int n,m,a[40];
ll ans=0;
ll gcd(ll x,ll y){
	ll z=x%y;
	while(z){x=y;y=z;z=x%y;}
	return y;
}
ll LCM(ll x,ll y){return x/gcd(x,y)*y;}
void dfs(int pos,ll lcm,int cnt){
	if(pos==0){
		if(lcm==1) return;//什么都不选的时候就没有意义啦
		if(cnt&1)	ans+=(m-17)/lcm;
		else ans-=(m-17)/lcm;
		return;
	}
	if(lcm>m) return;
	ll tmp=LCM(lcm,a[pos]);
	if(tmp<=m) dfs(pos-1,tmp,cnt+1);
	dfs(pos-1,lcm,cnt);	
}
int main(){
	n=in;m=in;
	int i,j,k;
	for(i=1;i<=n;++i) a[i]=in;
	sort(a+1,a+n+1);
	dfs(n,1,0);
	cout<<ans+1;
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值