傳送門:HDU 1796
常用方法有兩種:遞歸法和二進制枚舉法。
遞歸法是利用dfs的思想進行搜索,檢索每一種方案進行容斥。
二進制枚舉的方法最大的好處是能夠枚舉出所有元素組合的不同集合。假設一個集合的元素有m個,則對於m長的二進
制數來說就有m個1或0的位置,對於每一個1就對應一個元素。
整個二進制枚舉完就是所有子集,從0到2^m就行。[0, 2^m)
題意:給定一個數n,數列m個數,求這小於n的數中,有多少個數滿足能被這m個數中任意一個數整除。
思路:1~n之間有多少個能被x整除的數,公式爲n/x,題目中要求小於n,所以(n-1)/x。
可以遞歸法求,需要保存中間態重疊x次的最小公倍數lcm,符合題意的數有(n-1)/lcm個,根據k表示重疊的次數進
行或者加上,或者減去。
也可以用二進制枚舉法,將符合條件的m個數,看作m位,每位是0或者是1,那麼一共有2^m種狀態,只要判斷一下每
一個狀態有多少個1,也就是有多少個數(重疊多少次),記爲k,每一個1代表哪幾個具體的數,求這幾個數的最小
公倍數,然後(n-1)/lcm, 利用k的值來判斷應該減去還是加上即可。
code1:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long LL;
const int maxn = 110;
int gcd(LL a, LL b) {
return b == 0 ? a : gcd(b, a%b);
}
LL n;
int a[maxn], cnt,ans;
void dfs(int pos, LL lcm, int id);
int main()
{
//freopen("Text.txt", "r", stdin);
int m, i;
while (scanf("%lld %d", &n, &m) != EOF) {
int x;
cnt = 1; ans = 0;
for (i = 1; i <= m; i++) {
scanf("%d",&x);
if (x != 0)
a[cnt++] = x;
}
dfs(0, 1, 1);
printf("%d\n", ans);
}
}
void dfs(int pos, LL lcm, int id) {
//id表示重疊次數
for (int i = pos+1; i < cnt; i++) {
LL lcm_now = a[i] / gcd(a[i], lcm)* lcm;
if (id & 1)ans += (n - 1) / lcm_now;
else ans -= (n - 1) / lcm_now;
dfs(i, lcm_now, id+1);
}
}
code2:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long LL;
const int maxn = 110;
int gcd(LL a, LL b) {
return b == 0 ? a : gcd(b, a%b);
}
int main()
{
//freopen("Text.txt", "r", stdin);
LL n;
int a[maxn], m, i;
while (scanf("%lld %d", &n, &m) != EOF) {
int cnt = 0,x,ans=0;
for (i = 1; i <= m; i++) {
scanf("%d",&x);
if (x != 0)
a[cnt++] = x;
}
for (i = 1; i < 1 << cnt; i++) {//枚舉2^cnt個狀態
LL id = 0, lcm = 1;
for (int j = 0; j < m; j++) {//掃描每個狀態有多少個1,這裏記爲id
if (i&(1 << j)) {
lcm = lcm / gcd(lcm, a[j])*a[j];//求id個數的最小公倍數
id++;
}
}
//利用公式n/x求1-n能被x整除的個數
if (id & 1)ans += (n - 1) / lcm;
else ans -= (n - 1) / lcm;
}
printf("%d\n", ans);
}
}