poj3904

题意:给出n(n<10000)个数,这些数<=10000,要求选出四个数字且他们的最大公约数为1的(注意:不需要两两互质),有多少种选法。

分析:

容斥原理

假设平面上有一些圆,互相之间有重叠部分,我们要求这些圆覆盖的总面积(重叠部分只记一次)。计算方法就是:加上所有被覆盖了至少1次的面积加,减去所有被覆盖了至少2次的面积,加上所有被覆盖了至少3次的面积……对于奇数的就加,对于偶数的就减,最终结果即为覆盖的总面积。把这些圆变成是集合的话,那么这个计算过程就是容斥原理。

本题可以先计算n个数字选四个总共有多少种选法,然后减去公约数不是1的。把共同具有某一素因子的数字划分到同一个集合,属于统一集合的四个数最大公约数一定大于1,如果四个数字不同时属于任何集合则最大公约数一定为1。用总的组合数减去那些四个数字同时被同一个集合包括了的组合数,所得结果即为最终答案。但是这些一个四个数字的组合可能同时属于若干个集合,因此需要在这些集合之间进行容斥原理,以求每个需要被减去的四个数字的组合都恰好被减掉一次。

有一个统计数组,统计每个部分(集合或者集合的交集)中包含多少个数字(实际应该统计四个数字组合数,但是由于组合数可以通过数字数求得,在此为了方便只统计数字数)。实际操作时可以对每个数字分解质因数,把这些质因数在统计数组中的对应位加1。同时也要计算那些集合的重叠部分,也就是同时包含若干个质因子的情况,所以把每个数字质因子的所有组合方式相乘得到的数在统计数组中的对应位加1。还要记录它包含了几个质因子(是几个集合相交的交集)。以便以后容斥原理观察奇偶性时使用。

现在我们知道了所有部分包含的数字数量,但是计算的时候注意,我们求的不是数字数量,而是四个数字组合的数量。所以容斥原理加减过程加减的是四个数字的组合数。

#include <cstdio>
#include <cstdlib>
#include <cstring>
using namespace std;

#define MAX_N 100005
#define MAX_FACTOR 16

int n, f[MAX_N];
int count[MAX_N];
int factor[MAX_FACTOR];
int prime_fac_num[MAX_N];

void input()
{
    for (int i = 0; i < n; i++)
        scanf("%d", &f[i]);
}

void make(int a)
{
    int factor_cnt = 0;
    for (int i = 2; i * i <= a; i++)
        if (a % i == 0)
        {
            factor[factor_cnt++] = i;
            while (a % i == 0)
                a /= i;
        }
    if (a > 1)
        factor[factor_cnt++] = a;
    for (int i = 1; i < (1 << factor_cnt); i++)
    {
        int used_fac = 0;
        int cur_fac = 1;
        for (int j = 0; (i >> j) > 0; j++)
        {
            if ((i >> j) & 1)
            {
                used_fac++;
                cur_fac *= factor[j];
            }
        }
        count[cur_fac]++;
        prime_fac_num[cur_fac] = used_fac;
    }

}

long long cal(long long a)
{
    return a * (a - 1) * (a - 2) * (a - 3) / 24;
}

int main()
{
    while (scanf("%d", &n) != EOF)
    {
        input();
        memset(count, 0, sizeof(count));
        memset(prime_fac_num, 0, sizeof(prime_fac_num));
        for (int i = 0; i < n; i++)
            make(f[i]);
        long long ans = cal(n);
        for (int i = 2; i <= 10000; i++)
        {
            if (prime_fac_num[i] & 1)
                ans -= cal(count[i]);
            else
                ans += cal(count[i]);
        }
        printf("%lld\n", ans);
    }
    return 0;
}
View Code

 

posted @ 2013-11-21 00:42  金海峰  阅读(642)  评论(0编辑  收藏  举报