Sky Code
Stancu likes space travels but he is a poor software developer and will never be able to buy his own spacecraft. That is why he is preparing to steal the spacecraft of Petru. There is only one problem – Petru has locked the spacecraft with a sophisticated cryptosystem based on the ID numbers of the stars from the Milky Way Galaxy. For breaking the system Stancu has to check each subset of four stars such that the only common divisor of their numbers is 1. Nasty, isn’t it? Fortunately, Stancu has succeeded to limit the number of the interesting stars to N but, any way, the possible subsets of four stars can be too many. Help him to find their number and to decide if there is a chance to break the system.
Input
In the input file several test cases are given. For each test case on the first line the number N of interesting stars is given (1 ≤ N ≤ 10000). The second line of the test case contains the list of ID numbers of the interesting stars, separated by spaces. Each ID is a positive integer which is no greater than 10000. The input data terminate with the end of file.
Output
For each test case the program should print one line with the number of subsets with the asked property.
Sample Input
4
2 3 4 5
4
2 4 6 8
7
2 3 4 5 7 6 8
Sample Output
1
0
34
题意:
问给n个数,问这n个数中可以做成多少个四元组使得四个数互质,注意每两个数不用两两互质
分析:
我们知道n个数总的可以组成的四元组的个数为C4nCn4
因为直接求四个数互质的四元组有多少个比较难求,因此我们转而求不互质的四元组有多少个,即公因数为d的(d>1)的四元组,然后最终用C4nCn4减去
那么不互质的四元组有多少个怎么求呢?
我们首先可以想到公因数为d那么d到底是几?它可以是任何大于1的数,所以我们从2开始枚举d
那么确定下了d后我们知道四元组中四个数如果gcd是d的话那么说明四个数的因数得含有的d,因此我们需要知道给定的n个数中到底有几个数它的因子有d,一旦我们能知道有多少个数因子含有d,那么我们就可以用组合数算出这样的四元组的个数,假设含有因数d的数字有cnt个,那么可以构成的四元组就有C4cntCcnt4个
这个cnt怎么求呢?
对于每个给定的数我们可以对其进行质因数分解,得到不同的质因数,然后利用二进制枚举所有不同的组合方式,得到因数,说明这个数包含了这个因数,那么包含这个因数的数字的个数就可以加1,我们可以用cnt数组记录,cnt[i]表示含有因子i的数有多少。
那么知道了每种gcd为d(d>1)的四元组个数了,总的加起来就可以了吗?
当然不是因为里面会有重复
举个例子我们会枚举公因数为2的,为3的,为6的四元组的个数而公因数为6的四元组中一定有公因数为2和3的四元组,因为6=2×3,所以造成了重复
为了解决这个问题才引出这道题的主要算法
容斥定理
当公因数d中含有奇数个不同的质因子的时候为加,而为偶数个不同的质因子的时候为减,因为正如上面例子,奇数的时候(从1开始只含一个质因子)作为开始,偶数的时候就会出现重复,这样使用容斥定理就能消除多加多减的影响。
而记录因数d含有多少个不同质因子在上面二进制枚举质因子组合的时候顺便记录下来就好了
code:
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
typedef long long ll;
const int maxn = 1e4+10;
int n,m;
int cnt[maxn];//cnt[i]记录所给数据中,含有因数i的个数有几个
int num[maxn];//num[i]记录所给数据中,因数i中含有几个不同素因子
ll c[maxn];//c[i]表示组合数C(i,4)
int prime[maxn];
void init(){
for(ll i = 4; i < maxn; i++){
c[i] = i * (i - 1) * (i - 2) * (i - 3) / 24;
}
}
void divide(int n){
int tot = 0;
for(int i = 2; i * i <= n; i++){
if(n % i == 0){
prime[tot++] = i;
while(n % i == 0) n /= i;
}
}
if(n > 1) prime[tot++] = n;//分解质因子
for(int i = 1; i < (1<<tot); i++){//枚举质因子组合情况得到所有可能因子
int tmp = 1;
int sum = 0;
for(int j = 0; j < tot; j++){
if(i & (1 << j)){
tmp *= prime[j];
sum++;
}
}
cnt[tmp]++;//说明数n包含tmp这种因子,所以个数+1
num[tmp] = sum;//tmp这种因子内含有sum个不同质因子
}
}
int main(){
init();
memset(num,0,sizeof(num));
while(scanf("%d",&n) != EOF){
memset(cnt,0,sizeof(cnt));
for(int i = 0; i < n; i++){
scanf("%d",&m);
divide(m);//质因子分解统计相关数据
}
ll ans = 0;
for(int i = 2; i < maxn; i++){
if(cnt[i] >= 4){
if(num[i] & 1) ans += c[cnt[i]];//因子i中含有的质因子数为奇数加
else ans -= c[cnt[i]];//否则减
}
}
printf("%lld\n",c[n]-ans);
}
return 0;
}