HDU 5072 容斥原理 + 质因数分解

HDU 5072
题目链接:
http://acm.hdu.edu.cn/showproblem.php?pid=5072
题意:
给n个数,问有多少对数(a,b,c)使得gcd(a,b)=gcd(b,c)=gcd(a,c)=1,或者gcd(a,b),gcd(b,c),gcd(a,c)均不为1。
思路:
单求为1的倒还好办,因为数字都在1e5以内应该可以用一种质因数分解的方式求出来,但是第二个条件就不好求了。
所以问题转化一下,求tans = 不合法的对数,然后ans = 总数 - tans。
举了几个例子发现,对于每个数求出与他互质的数的个数u,剩下的就是不与他互质的数(n-1-u),则tans = sigma(u*(n-1-u)) / 2。证明的话大概用容斥原理。
然而怎么求与他互质的数的个数……然后就卡死了。
从网上搜解题报告,还是利用容斥原理来求。
对于一个数,质因数分解得到它的质因数数组ele[],然后每个ele最多使用一次能构成的倍数,给这些数的计数器+1,表示此数不与这些数互质。
在求与一个数互质的数dp时,进行同样的质因数分解得到质因数数组ele[],每个ele最多使用一次构成倍数temp。如果ele使用了奇数个,dp+=num[temp](num是计数器,temp表示这个倍数),否则dp-=num[temp]。具体可以用全概率公式的原理来解释。
源码:

#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <algorithm>
#include <iostream>
#include <string>
using namespace std;
#define LL long long
const int MAXN = 200000 + 5;
int vis[MAXN];
int g[MAXN];
int num[MAXN];
int p[MAXN], cnt;
int data[MAXN];
void init()
{
    memset(vis, 0, sizeof(vis));
    cnt = 0;
    for(int i = 2 ; i < MAXN ; i++){
        if(vis[i] == 0){
            p[cnt++] = i;
            int u = i;
            while(u < MAXN) vis[u] = 1, u += i;
        }
    }
}
int ele[MAXN];
int main()
{
//    freopen("C.in", "r", stdin);
    init();
    int t;
    scanf("%d", &t);
    while(t--){
        int n;
        scanf("%d", &n);
        memset(g, 0, sizeof(g));
        for(int i = 0 ; i < n ; i++)
            scanf("%d", &data[i]), g[data[i]] = 1;
        memset(num, 0, sizeof(num));
        for(int i = 0 ; i < n ; i++){
            cnt = 0;
            int temp = data[i];
            for(int j = 0 ; p[j] * p[j] <= temp ; j++){
                if(temp % p[j] == 0){
                    ele[cnt++] = p[j];
//                    printf("cnt = %d, p[j] = %d, temp = %d\n", cnt, p[j], temp);
                    while(temp % p[j] == 0){
                        temp /= p[j];
//                        printf("temp = %d, p[j] = %d\n", temp, p[j]);
                    }
                }
            }
            if(temp != 1)   ele[cnt++] = temp;
            for(int j = 0 ; j < (1 << cnt) ; j++){
                int which = 1;
                for(int k = 0 ; k < cnt ; k ++){
                    if((1 << k) & j)
                        which *= ele[k];
                }
                num[which]++;
            }
        }
//        printf("second\n");
        LL ans = 0;
        for(int i = 0 ; i < n ; i++){
            cnt = 0;
            int temp = data[i];
            for(int j = 0 ; p[j] * p[j] <= temp ; j++){
                if(temp % p[j] == 0){
                    ele[cnt++] = p[j];
                    while(temp % p[j] == 0) temp /= p[j];
                }
            }
            if(temp != 1)   ele[cnt++] = temp;
            LL tans = 0;
            for(int j = 1 ; j < (1 << cnt) ; j++){
                int sta = 0;
                LL which = 1;
                for(int k = 0 ; k < cnt ; k++){
                    if((1 << k) & j){
                        sta++,  which *= ele[k];
                    }
                }
                if(which > MAXN)    continue;
                if(sta & 1) tans += num[which];
                else    tans -= num[which];
            }
            if(tans > 0)    tans--;
            ans += tans * (n - 1 - tans);
        }
        ans = (LL)n * (n - 1) * (n - 2) / 6 - ans / 2;
        printf("%I64d\n", ans);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值