Sky Code POJ - 3904
考察:容斥原理
错误思路:
枚举预处理每一个数C4n 的组合数,将p数组的每一个数求约数集合,将约数个数>=4的纳入容斥的集合内.再用容斥计数解决.
时间复杂度是10000*100*2出现次数>=4的约数个数 ,前面的时间复杂度已经到了1e6,如果个数>7就已经有超时风险,更不要说总共10000个数字
正确思路:
上面的思路会想到枚举约数是因为,当质数集合选择两个以上时,我们需要减去质数积在集合出现的次数.这部分在分解质因数是难以计算的.但我们可以利用容斥的二进制枚举思想,我们只计算质数积的出现次数,而避免分解所有约数.这样的话我们需要用一个数组累计素数的出现次数,另一个累计素数积的出现次数.当我们在容斥板子里枚举两个,我们要计算合数数组里取4个的组合数.
但有更好的优化方法,避免合数数组,我们在二进制枚举时就直接计算所有质因数能组成的因数,统计他们出现次数和有几个因子组成,在main函数里,从2~N遍历一遍,如果出现次数>=4且因子数为奇就减去,为偶就加上.也就是说,原本在容斥板子里要用的二进制枚举所有质因数以及质因数的乘积在分解质因数里已经完成了
这种优化方法像是把容斥拆成两部分完成...
时间复杂度: 10000*(100+25)+10000(an<=10000,所以每个能分解的质数集合很小)
易错:
- 计算组合数组请不要忘记long long,都忘两次了!!!
1 #include <iostream> 2 #include <algorithm> 3 #include <cstdio> 4 #include <cstring> 5 using namespace std; 6 typedef long long ll; 7 const int N = 1e4+10; 8 int p[N],prime[1600],nums[N],tmp[N]; 9 ll C[N]; 10 void GetDivide(int n) 11 { 12 int cnt = 0; 13 for(int i=2;i<=n/i;i++)//统计一个数n的质因子 14 { 15 if(n%i==0) 16 { 17 prime[cnt++] = i; 18 while(n%i==0) n/=i; 19 } 20 } 21 if(n>1) prime[cnt++] = n; 22 for(int i=1;i<1<<cnt;i++)//这样可以保证因子只能由质数组成 23 { 24 ll res = 1; int sz = 0; 25 for(int j=0;j<cnt;j++) 26 { 27 if(i>>j&1) 28 { 29 sz++; 30 res*=prime[j]; 31 } 32 } 33 nums[res]++;//含有res因子的数目 34 tmp[res] = sz;//res含有的因子数目 35 } 36 } 37 void inits() 38 { 39 for(int i=4;i<=N-10;i++) C[i] = (ll)i*(i-1)/2*(i-2)/3*(i-3)/4;//请不要忘了.... 40 } 41 int main() 42 { 43 inits(); 44 int n; 45 while(scanf("%d",&n)!=EOF) 46 { 47 ll ans =C[n]; memset(nums,0,sizeof nums); memset(tmp,0,sizeof tmp); 48 for(int i=1;i<=n;i++) scanf("%d",&p[i]); 49 for(int i=1;i<=n;i++) GetDivide(p[i]); 50 for(int i=2;i<=N-10;i++) 51 { 52 if(nums[i]>=4)//用了容斥的思想 53 { 54 if(tmp[i]&1) ans-=C[nums[i]]; 55 else ans+=C[nums[i]]; 56 } 57 } 58 printf("%lld\n",ans); 59 } 60 return 0; 61 }