0
点赞
收藏
分享

微信扫一扫

容斥原理的妙用

Java旺 2022-03-31 阅读 47

容斥原理的定义

简介:

例题1 区间整除数

给出一个数组 a [ 1.. n ] a[1..n] a[1..n],问在区间 [ L , R ] [L,R] [L,R]中有多少个数,至少能被a中的一个数整除。
输入格式

多组测试数据。

第一行,一个整数 G G G,表示有 G G G组测试数据。 1 < = G < = 10 1 <= G <= 10 1<=G<=10

每组测试数据格式:

第一行,三个整数, N N N L L L R R R 1 < = L < = R < = 1 0 9 1 <= L<=R <= 10^9 1<=L<=R<=109, $ 1<=N<=18$。

第二行, N N N个整数,第 i i i个整数是 a [ i ] a[i] a[i]。 $ 1 <= a[i] <= 10^9$。

输出格式

G G G行,每行一个整数。

输入

8

1 293 784

1

1 255 734

2

2 579000 987654

1 2

2 1 1000000000

2 3

3 1 1000000000

2 3 5

18 1 1000000000

2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 49 53 59

2 1956 9013

307 419

5 8636 9586

5 367 487 463 167

输出

492

240

408655

666666667

733333334

866219330

40

200

分析

题目询问区间 [ l , r ] [l,r] [l,r] 可以转化为 [ 1 , r ] − [ 1 , l − 1 ] [1,r]-[1,l-1] [1,r][1,l1]

此时我们只需求 [ 1 , m a x ] [1,max] [1,max]中有多少个数能至少被一个 a [ i ] a[i] a[i]整除即可

直接从枚举 [ 1 , m a x ] [1,max] [1,max]必然会超时

于是考虑如下方法:

我们可以算 [ 1 , m a x ] [1,max] [1,max]中有多少个数会被 a [ 1 ] a[1] a[1]整除 有 m a x / a [ 1 ] max/a[1] max/a[1]

我们可以算 [ 1 , m a x ] [1,max] [1,max]中有多少个数会被 a [ 2 ] a[2] a[2]整除 有 m a x / a [ 2 ] max/a[2] max/a[2]

我们可以算 [ 1 , m a x ] [1,max] [1,max]中有多少个数会被 a [ n ] a[n] a[n]整除 有 m a x / a [ 1 ] max/a[1] max/a[1]

此时我们会发现,是 a [ 1 ] ∗ a [ 2 ] a[1] * a[2] a[1]a[2] 的倍数的数会被我们重复算

于是我们要减去 m a x / ( a [ 1 ] ∗ a [ 2 ] ) max/(a[1] * a[2]) max/(a[1]a[2])

然后我们再加上 m a x / ( a [ 1 ] ∗ a [ 2 ] ∗ a [ 3 ] ) max/( a[1] * a[2] * a[3]) max/(a[1]a[2]a[3])

依次类推,我们可以发现,

  • 当取奇数个数时,ans要加
  • 当取偶数个数时,ans要减

于是我们可以通过二进制枚举,枚举数组a的组合,进行容斥

还有一个小细节,

当我们取4和6两个数时,

下一个可以被他们同时整除的数是 l c m ( 4 , 6 ) = 12 lcm(4,6)=12 lcm(4,6)=12,而不是 4 ∗ 6 = 24 4 * 6 = 24 46=24

所以我们应该取所有数的最小公倍数

当所有数相乘已经超过 m a x max max时,它对答案的贡献一定是0,就可以不用算下去了

时间复杂度为 O ( G ∗ 2 n ) O(G * 2^n) O(G2n)

代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
long long g,n,l,r,a[20],b[20],len=0;
long long check(ll R){
	long long sum=0;
	for(ll i=1;i<(1<<n);i++){
		ll cnt=0,ans=1;
		bool lim=false;
		for(ll j=0;j<n;j++){
			if((1<<j)&i){
				if(ans*a[j]/__gcd(ans,a[j])>R){
					lim=true;
					break;
				}
				ans=ans*a[j]/__gcd(ans,a[j]);
				cnt++;
			}
		}
		if(lim)continue;
		if(cnt&1)sum+=R/ans;
		else sum-=R/ans;
	}
	return sum;
}
int main(){
	cin>>g;
	while(g--){
		len=0;
		cin>>n>>l>>r;
		for(ll i=0;i<n;i++){
			cin>>a[i];
		}
		sort(a,a+n);
		cout<<check(r)-check(l-1)<<endl;
	}
	return 0;
}

例题二 和谐

研究证明,

有一个因素在两头奶牛能否作为朋友和谐共处这方面比其他任何因素都求得重要——

她们是不是喜欢同一种口味的冰激凌!

Farmer John的N头奶牛 ( 2 ≤ N ≤ 5 ∗ 1 0 4 ) (2 \leq N \leq 5 * 10^4) (2N5104)各自列举了她们最喜欢的五种冰激凌口味的清单。

为使这个清单更加精炼,每种可能的口味用一个不超过 1 0 6 10^6 106的正整数ID表示。

如果两头奶牛的清单上有至少一种共同的冰激凌口味,那么她们可以和谐共处。

请求出不能和谐共处的奶牛的对数。

输入:

4

1 2 3 4 5

1 2 3 10 8

10 9 8 7 6

50 60 70 80 90

输出:

4

分析:

题目问的是不能和谐相处奶牛的对数,我们可以考虑他的反面,

算出有多少对奶牛可以和谐相处,然后用总对数 C n 2 C_n^2 Cn2减去可以和谐相处的奶牛的对数即可

我们可以查找第i只奶牛的前面有多少只可以与这头奶牛和谐相处,这样算的话每一对和谐相处的奶牛就可以只被算一次,就不用再去重

首先考虑对每一个奶牛的 I D ID ID 进行排序,然后使用一个字符串将编号连接起来

然后存进 m a p map map里面,记录到现在有多少头奶牛的 I D ID ID组合是相同的,记录相同的奶牛的头数

对于一只奶牛,它只有5个 I D ID ID,有 2 5 2^5 25种组合,我们可以像上一题一样,

加上都有 I D 1 ID_1 ID1的奶牛数,加上都有 I D 2 ID_2 ID2的奶牛数,再减去都有 I D 1 ID_1 ID1 I D 2 ID_2 ID2的奶牛数…

同理,当选了奇数个 I D ID ID时, a n s ans ans要加,选了偶数个 I D ID ID时, a n s ans ans要减

最后不要忘记在 m a p map map里加上这头奶牛

代码:

#include<bits/stdc++.h>
using namespace std;
long long n;
string a[10];
long long ans=0;
map<string,long long>d;
int main(){
	cin>>n;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=5;j++)cin>>a[j];
		sort(a+1,a+5+1);
		for(int j=1;j<(1<<5);j++){
			string str="";
			int cnt=0;
			for(int k=1;k<=5;k++){
				if(j&(1<<(k-1))){
					str=str+"_"+a[k];
					cnt++;
				}
			}
			
			if(cnt&1)ans+=d[str];
			else ans-=d[str];
			d[str]++; 
		}
	}	
	cout<<n*(n-1)/2-ans<<endl;
	return 0;
}

例题三 完全平方数

P4318 完全平方数

分析:

观察数据 1 ≤ K ≤ 1 0 9 1 ≤ K ≤ 10^9 1K109 ,考虑二分

此时转化成为求在[1,max_num]中,有多少个数不是完全平方数或完全平方数的整数倍的数

我们可以从反面考虑,在[1,max_num]中,有多少个数是完全平方数或完全平方数的整数倍的数,再用max_num去减

此时我们会发现,当我们枚举时,42已经是22的倍数了,92已经是32的倍数了,所以我们只需要枚举质数的完全平方数

我们可以先打出一个质数表,然后像上两题一样,选2时,加上max_num/(22),选3时,加上max_num/(32),选2和3时,减去max_num/(2^2 * 3^2)…

时间复杂度是 O ( 2 n ) O(2^n) O(2n)级别的,明显会超时,因此我们需要进行优化

依然像上题一样,我们需要的只是这些数的乘积,而不需要具体知道选了哪些数,因此我们可以枚举[1,max_num]里的数

还是会超时

我们再次发现,这些数相乘之后要平方,所以我们只用枚举 [ 1 , m a x _ n u m ] [1,\sqrt {max\_num}] [1,max_num ]里面的数,时间复杂度降下来了很多

枚举的时候,如果还没有平方后的数分解质因数后有两个或以上的质因子,证明这个数对答案没有帮助,因为我们不能选两个相同的质数。

于是,我们可以开一个数组d,当有奇数个质因子时,d[i]=1,有偶数个质因子时,d[i]=-1,否则d[i]=0

此时时间复杂度已经有了很大的优化,但是依然很危险

于是我们引入莫比乌斯函数:

若我们此时已经知道了 [ 1 , m a x _ n u m ] [1,\sqrt {max\_num}] [1,max_num ]里所有的莫比乌斯函数值,观察发现,他正好与我们的d数组相反,

因此我们就可以直接得到在[1,max_num]中,有多少个数不是完全平方数或完全平方数的整数倍的数,不需要再用max_num去减。

那我们该怎样求莫比乌斯函数的值呢?

我们可以用欧拉筛(线性筛)来求,于是代码就出来了。

代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll max_num=1644934082,max_num2=40559,max_num3=4253;
ll t,x,mark[max_num2+5],mu[max_num2+5],pri[max_num3+5],cnt=0;
void init(){
	mu[1]=1;
	for(ll i=2;i<=40559;i++){
		if(!mark[i])pri[++cnt]=i,mu[i]=-1;
		for(ll j=1;j<=cnt&&pri[j]*i<=40559;j++){
			ll x=pri[j];
			mark[x*i]=true;
			if(i%x==0){
				mu[i*x]=0;
				break;
			}
			else mu[i*x]=-mu[i];
		}
	}
}
ll check(ll num){
	ll ans=0;
	for(ll i=1;i*i<=num;i++){
		ans+=mu[i]*(num/(i*i));
	}
	return ans;
}
ll Q(){
	ll l=x-1,r=max_num+1;//paiming
	while(l+1<r){
		ll mid=(l+r)>>1;
		if(check(mid)<x)l=mid;
		else r=mid;
	}
	return r;
}
int main()
{
	init();
	scanf("%lld",&t);
	while(t--){
		scanf("%lld",&x);
		printf("%lld\n",Q());
	}
	
	return 0;
}
举报

相关推荐

0 条评论