POJ 3904 Sky Code (互素的四元组+容斥原理)

POJ 3904 Sky Code (互素的四元组+容斥原理) :http://poj.org/problem?id=3904

题目大意:

给定n个数据,求其中互素的四元组(两两可以不互素)的个数。

题目分析:

要想求互素的四元组的个数,我们可以先去求不互素的四元组的个数ans,然后所有可能的取法数C(n,4)(n个里面取4个的组合数公式)减去不互素的四元组的个数。

考虑到不互素的四元组必满足四个数必有除了1以外的大于等于一个的因子(可能是素数,也可能不是素数),则可以对每一个数都进行因子分解,由于这些因子可能会被重复的计算,所以应用容斥原理,对于每一个因子,该因子若含有除了1以外的奇数个因子的话,则加上,否则,含有偶数个因子的话,则减去;举例说明:n个数中,以2为因子的数有a个,以3为因子的数有b个,以6为因子的数有c个,则满足不互素的四元组的个数有C(a,4)+C(b,4)-C(c,4)个。

关于代码实现中的二进制枚举部分,有几个关键的地方:(转)

(1): 理解cnt[i]表示含有因子i的数据的个数(2,4,6,8,10这组数据中cnt[i]=5) num[i]表示因子i含有的不同素因子的个数(num[6]=2,减)

(2): 怎样得到cnt[i]、num[i],或者说怎样实现素因子组合?用到了二进制表示的思想,第j位的1表示第j个素因子参与累乘,0表示不参与累乘,比如含有素因子2*3*5*7,1010表示2*1*5*1,0001表示1*1*1*7,以此类推……这样,只要素因子分解完记录含有素因子的个数tol,那么就有tol位二进制,注意到2*3*5*7*11*13>10000,所以最多有6位二进制;对于任意一个范围内的数i,遍历所有位j(从低位到高位),i&(1<<j)==1,表示i的第j位二进制为1,意思是第j个素因子参与累乘;依据这样的意义,遍历二进制范围内的所有数i和i对应的所有二进制数位j,更新数组cnt[k]和num[k]即可!

代码实现:

#include <iostream>
#include <cstdio>
#include <cstring>

using namespace std;

const int MAXN=10005;

long long  prime[MAXN];  //存放素因子
long long C[MAXN];
long long count[MAXN];
long long num[MAXN];

void zuheshu() //计算组合数,i个里面选4个
{
    memset(C,0,sizeof(C));
    for(long long i=4; i<=10000; i++)
    {
        C[i]=i*(i-1)*(i-2)*(i-3)/24;
    }
}

void divide(int x)
{
    long long tol=0;
    for(long long i=2;i*i<=x;i++)
    {
        if(x%i==0)
        {
            prime[tol++]=i;
        }
        while(x%i==0)
        {
            x/=i;
        }
    }
    if(x!=1)
    {
        prime[tol++]=x;
    } 
    //对x进行素因子分解,tol表示素因子的个数
    for(long long i=1;i<(1<<tol);i++) //利用二进制的每一位0,1对素因子进行组合
    {
        long long k=1;
        long long sum=0;
        for(long long j=0;j<tol;j++) //从低位到高位遍历,找i在哪一位是1
        {
            if(i&(1<<j)) //与运算,j位为1,执行if语句
            {
                k*=prime[j];//累乘素因子
                sum++;
            }
        }
        count[k]++; //记录当前因子的个数
        num[k]=sum; //记录当前因子是由多少个素因子组成
    }
}

int main()
{
    long long  n,m;
    zuheshu();
    while(scanf("%lld",&n)!=EOF)
    {
        memset(count,0,sizeof(count));
        memset(num,0,sizeof(num));
        for(int i=0; i<n; i++)
        {
            scanf("%lld",&m);
            divide(m); //素因子分解,并统计
        }
        long long ans=0;
        for(long long i=0;i<=10000;i++)
        {
            if(count[i])
            {
                if(num[i]%2)
                {
                    ans+=C[count[i]];
                }
                else
                ans-=C[count[i]];
            }
        }
        printf("%lld\n",C[n]-ans);
    }
    return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值