题目链接:http://poj.org/problem?id=3904
题目大意:
给出一个数列,询问从中取4个元素满足最大公约数为1的方案数
题解:
很显然,ans=总的方案数-最大公约数大于1的4个元素的组合的方案数
=总的方案数-存在公约数大于1的4个元素的组合的方案数
考虑后者如何计算
容斥一下
后者=含有一个质因子(不仅仅是一个,应该是至少一个,后面的同理)的元素的个数取4的方案数-含有两个质因子的元素的个数取4的方案数+含有三个质因子的元素的个数取4的方案数...
朴素做法是枚举质因子,判断当前的质因子的公倍数有几个然后统计答案,但是我们还有其他的方法
怎么做呢?我们是不是可以枚举约数,根据这个约数由几个不同的质因子组成判断是+号还是-号(质因子不可以重复),并且在已知下它的倍数在数列中出现的次数计算对答案的贡献
具体实现就是对于数列中的每一个元素处理它的不同质因子组合成的约数,注意这些约数的质因子不可以重复。这样的话,当我们枚举到这个约数的时候,我们就可以直接知道上述的两个信息
一个小理解:注意一个数可能在一个质因子的时候产生贡献,同时也在多个质因子的时候同样产生贡献
#include<iostream> #include<cstring> #include<algorithm> #include<cstdio> #include<vector> using namespace std; typedef long long ll; const int N=1e4+15; int n; int a[N],num[N],cunt[N]; vector <int> pr; void div(int x) { pr.clear(); for (int i=2;i*i<=x;i++) { if (x%i) continue; pr.push_back(i); while (x%i==0) x/=i; } if (x>1) pr.push_back(x); } void solve(int x) { div(x); int size=pr.size(); int all=(1<<size); for(int i=1;i<all; i++) { ll t=1,ci=0; for(int j=0;j<size;j++) { if(i&(1<<j)) { t*=pr[j]; ci++; } } cunt[t]++;//该不含重复质因子的约数的倍数出现的次数 num[t]=ci;//记录下含有几个不同的质因子 } } ll C(ll x) { return x*(x-1)*(x-2)*(x-3)/24; } int main() { while (cin>>n) { memset(cunt,0,sizeof(cunt)); for (int i=1;i<=n;i++) scanf("%d",a+i),solve(a[i]); ll res=0; for (int i=1;i<=10000;i++) if (cunt[i]) { if (num[i]&1) res+=C(cunt[i]); else res-=C(cunt[i]); } res=C(n)-res; printf("%lld ",res); } return 0; }