1. 程式人生 > 實用技巧 >八 (容斥)

八 (容斥)

題目描述

八是個很有趣的數字啊。

八=發,八八=爸爸,88=拜拜。

當然最有趣的還是 \(8\) 用二進位制表示是 \(1000\)

怎麼樣,有趣吧。當然題目和這些都沒有關係。

某個人很無聊,他想找出 \([a,b]\) 中能被 \(8\) 整除卻不能被其他一些數整除的數。

輸入格式

第一行一個數 \(n\) ,代表不能被整除的數的個數。

第二行 \(n\) 個數,中間用空格隔開。

第三行兩個數 \(a,b\) ,中間一個空格。

輸出格式

一個整數,為 \([a,b]\) 間能被 整除卻不能被那 \(n\) 個數整除的數的個數。

樣例

樣例輸入

  3
  7764 6082 462
  2166 53442

樣例輸出

  6378

資料範圍與提示

對於 \(30\%\) 的資料, \(1\leqslant n\leqslant 5,1\leqslant a\leqslant b\leqslant 10^5\)

對於 \(100\%\) 的資料,\(1\leqslant n\leqslant 15,1\leqslant a\leqslant b\leqslant 10^9\) 。個數全都小於等於 \(10^4\) 大於等於 \(1\)

分析

首先考慮沒有限制的情況,也就是求 \([a,b]\) 中能被 \(8\) 整除的數的個數,答案容易得到是 \(\frac{8}{r} - \frac{8}{l-1}\)

。接下來我們考慮怎麼去除同時也被限制的 \(n\) 個數整除的個數。假如現在限制只有一個,我們設為 \(x\) ,那麼我們只需要在上邊求出的答案裡減去一個 \(\frac{lcm(8,x)}{r} - \frac{lcm(8,x)}{l-1}\) 。這樣就把能被 \(8\) 整除但是不能被 \(x\) 整除的數算出來了。如果有多個也是一樣的,我們設全集 \(U\) 為限制的那 \(n\) 個數,我們只需要列舉這個全集的子集,每一次對於 \(8\) 和這個子集所有數共同的 \(lcm\) 做的貢獻進行加減,而加減的考慮要看集合元素的個數,如果是奇數個,就減去,偶數個就加上。(個人理解是奇數個的時候造成的貢獻在偶數個的時候去除重複的貢獻,手寫幾個應該就能看出來,如果不對請大佬指出 \(qaq\)
)。然後我們就用正常的狀壓 \(dp\) 搞一搞就行了。

Code

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn = 20;
ll a[maxn];
inline ll gcd(ll a,ll b){
	if(b == 0)return a;
	return gcd(b,a%b);
}
inline ll lcm(ll a,ll b){
	return a * b / gcd(a,b);
}
int main(){
	ll ans = 0;
	int n;
	scanf("%d",&n);
	for(int i = 1;i <= n;++i){
		scanf("%lld",&a[i]);
	}
	ll l,r;
	scanf("%lld%lld",&l,&r);
	ans += r / 8 - (l - 1) / 8;
	ll mx = (1 << n) - 1;
	for(ll s = 1;s <= mx;++s){
		ll LCM = 8;
		int cnt = 0;
		for(int i = 1;i <= n;++i){
			if(s & (1 << (i - 1)))LCM = lcm(LCM,a[i]),cnt++;
		}
		if(cnt & 1)ans -= r/LCM - (l - 1) / LCM;
		else ans += r / LCM - (l - 1) / LCM;
	}
	printf("%lld\n",ans);
	return 0;
}

\(Never\ Give\ Up\)