poj3904 Sky Code —— 唯一分解定理 + 容斥原理 + 组合

题目链接:http://poj.org/problem?id=3904

类似题目poj2773(题解):http://blog.csdn.net/DOLFAMINGO/article/details/72669432


题解:

给出n个数, 随便挑4个, 使得这四个数的最大公约数为1, 问有多少种组合?

思路:先用容斥原理计算出四个数的最大公约数>=1的组合数, 然后再用总数C(n,4)减之。


1.将每个数进行分解质因数, 然后再根据这些质因数组合出不同的因子,并记录这个因子出现的次数以及由多少个质因数构成。

2.容斥原理:比如因子2的个数为a,则四个数公约数为2的个数 为C(a,4),因子3的个数为b,则四个数公约数为3的个数为C(b,4),因子6(2*3)的个 数为c,则四个数公约数的个数为C(c,4)。 但是公约数为2的情况中或者公约数为3的情况中可能包括公约数为6的情况,相当于几个集合求并集,这就需要容斥定理来做。

3.如果这个因子出现的次数>=4, 则表明这个因子可以作为某四个数的最大公约数的因子。

4.根据容斥原理:当这个因子的由奇数个质因数构成时, 加; 当这个因子由偶数个质因子构成时, 减。

5. ans = C(n,4) - gcd(a,b,c,d)!=1的组合数。



代码如下:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <string>
#include <vector>
#include <map>
#include <set>
#include <queue>
#include <stack>
#include <sstream>
#include <algorithm>
using namespace std;
#define pb push_back
#define mp make_pair
#define ms(a, b)  memset((a), (b), sizeof(a))
#define eps 0.0000001
typedef long long LL;
const int INF = 2e9;
const LL LNF = 9e18;
const int mod = 1e9+7;
const int maxn = 1e4+10;

LL pri[maxn], fac_num[maxn], fac_pri[maxn];
LL n, cnt;

LL C(LL x)
{
    return x*(x-1)*(x-2)*(x-3)/24;
}

void Divide(LL x)
{
    cnt = 0;
    for(int i = 2; i*i<=x; i++)
    {
        if(x%i==0)
        {
            pri[cnt++] = i;
            while(x%i==0)  x /= i;
        }
    }
    if(x!=1) pri[cnt++] = x;
}

void Unit()
{
    for(LL s = 1; s < (1<<cnt); s++)   //用二进制, 亦可用递归
    {
        LL tmp = 1, sum = 0;
        for(int j = 0; j<cnt; j++)
        if(s&(1<<j))
        {
            tmp *= pri[j];
            sum++;
        }

        fac_num[tmp]++;
        fac_pri[tmp] = sum;
    }
}

void init()
{
    ms(fac_num, 0);
    ms(fac_pri, 0);

    LL x;
    for(int i = 1; i<=n; i++)
    {
        scanf("%lld",&x);
        Divide(x);  //分解质因数
        Unit();     //质因数可以组成哪些因子(这些因子就是四个数的约数)
    }
}

void solve()
{
    LL tmp = 0;
    for(int i = 1; i<=1e4; i++) //容斥, 计算gcd(a,b,c,d)!=1的个数
    {
        if(fac_num[i]>=4)   //这个因子的个数必须不小于4, 才能成为4个数的约束
        {
            if(fac_pri[i]&1)    //素数个数为奇数时, 加
                tmp += C(fac_num[i]);
            else                //素数个数为偶数时, 减
                tmp -= C(fac_num[i]);
        }
    }
    LL ans = C(n) - tmp;    //总的减去gcd(a,b,c,d)!=1的个数,即为gcd(a,b,c,d)=1的个数。
    printf("%lld\n", ans);
}

int main()
{
    while(scanf("%lld",&n)!=EOF)
    {
        init();
        solve();
    }
}


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值