容斥原理

来源:互联网 发布:九九乘法表js编程 编辑:程序博客网 时间:2024/06/05 01:54

传送门: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,这里记为idif (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);}}


    

原创粉丝点击