题目来源:http://acm.hdu.edu.cn/showproblem.php?pid=1796
How many integers can you find
Time Limit: 12000/5000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others)
Total Submission(s): 3539 Accepted Submission(s): 994
分析:
1:基本的容斥原理。给定一个N 和一个M集合, 找到小于n 的数中有多少是M中元素的整数倍的 个数。 S= n-1
设xi 为 S 中能被 M 中 i 个数整除的个数 则 Xans = x1 - x2 + x3 -……+(-1)^(m-1)xm。
需要注意的是 , 求能被i 个数整除的 个数的时候, 用(n-1)/ lcm(mi) , 即除以M集合中元素的最小公倍数。
2: 从组合数学上分析
设全集 为 S={1 2 3 , ... , n-1} |s| = n-1;
M = {m1, m2 , ... , mm}, 且 M中 个元素互素
令Qi(i=12, ,,, , m) 表示S中 能被 mi整除的整数这一性质, 令Ai 为S中 具有性质Qi的那些整数所组成的集合。则|A1 υ A2 υ ...υ Am| 表示S中能被任意mi整除的整数个数。
|S| - |A1 υ A2 υ ...υ Am| 表示 S中 不能被 mi (i = 1, 2 ,...,m) 整除的整数的个数。即S中 与 M 互质的个数。
又 |A1 υ A2 υ ...υ Am| = ∑|Ai| - ∑|Ai ∩ Aj| + ∑|Ai ∩ Aj ∩ Ak| -...+(-1)^(m-1)|A1 ∩ A2 ∩... ∩Am|
又 |Ai| = |S| / (mi) |Ai ∩ Aj| = |S| / (mi * mj) 同理
注意:如果M中各个元素不是 互素的 ,则 |Ai| = |S| / (mi) , |Ai ∩ Aj| = |S| / lcm(mi , mj) ,正是本题题意。
3:容斥原理 ,是采用 dfs 搜索, 分别对 M 中的 每个元素mi 枚举, 然后遍历mi ,搜索树,设树根深度为1, 当树深度为偶数,sum-= , 当 树深度为 奇数时 ,sum+=。
递归图如下:
本题代码如下:
#include<iostream> #include<stdlib.h> #include<stdio.h> #include<math.h> #include<string.h> #include<string> #include<queue> #include<algorithm> #include<map> using namespace std; typedef long long LL; // 大数 _int64 ,大数输出 // %I64d int n, M; int a[22]; // 存储的是M中的值 LL gcd(LL x, LL y) //最大公约数 { return y==0?x:gcd(y,x%y); } LL lcm(LL x, LL y ) // 最小公倍数 { return x*y/gcd(x,y); } // 当前点, 搜索树的深度, 记录容斥过程值 void dfs(int now, int count , LL lcm_1 , LL &sum) { lcm_1 = lcm(a[now], lcm_1); if(count&1) sum+=(n-1) / lcm_1; else sum-=(n-1) / lcm_1; for(int i=now+1 ; i<M ;i++) dfs(i,count+1 , lcm_1, sum); // 搜索方向 } int main() { int b, len; LL sum; while(scanf("%d%d",&n,&M)!= EOF) { sum=0; len=0; for(int i=0; i<M ; i++) { scanf("%d",&b); if(b) a[len++]= b; } M=len; for( int i=0;i<M;i++) // 枚举M中集合mi dfs(i , 1 ,a[i], sum); printf("%I64d ", sum); } return 0; }