HDU 5072 Coprime

计赛经典训练题解析
本文解析了一道算法竞赛中的经典题目,涉及容斥原理及质因数分解等技巧,通过构建数学模型来解决组合问题。

传送门

Coprime

Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 262144/262144 K (Java/Others)
Total Submission(s): 2074    Accepted Submission(s): 781


Problem Description
There are n people standing in a line. Each of them has a unique id number.

Now the Ragnarok is coming. We should choose 3 people to defend the evil. As a group, the 3 people should be able to communicate. They are able to communicate if and only if their id numbers are pairwise coprime or pairwise not coprime. In other words, if their id numbers are a, b, c, then they can communicate if and only if [(a, b) = (b, c) = (a, c) = 1] or [(a, b) ≠ 1 and (a, c) ≠ 1 and (b, c) ≠ 1], where (x, y) denotes the greatest common divisor of x and y.

We want to know how many 3-people-groups can be chosen from the n people.
 

Input
The first line contains an integer T (T ≤ 5), denoting the number of the test cases.

For each test case, the first line contains an integer n(3 ≤ n ≤ 105), denoting the number of people. The next line contains n distinct integers a1, a2, . . . , an(1 ≤ ai ≤ 105) separated by a single space, where ai stands for the id number of the i-th person.
 

Output
For each test case, output the answer in a line.
 

Sample Input
  
1 5 1 3 9 10 2
 

Sample Output
  
4
 

Source
 

题目大意:

给了n(n<100000)个不同的数,要求的就是有多少个三元组,两两互素 或者 两两不互素。


解题思路:

可以参考一下 《算法竞赛入门经典 训练指南》(白书)  p105 问题6  同色三角形。

所以这个题 可以考虑反面,所以就用到容斥原理。这题可以换个角度想,可以将三个数看做三角形的三条边,互质即边的颜色为1,否则为0,那么要求的即为三条边颜色相同的三角形有多少个。对于每个数字求出与其不互质的个数k   那么  sum ( k*(n-1-k) )/2 就是相反的数目, 求每个数互质或者不互质的数有多少个,将每个数质因子分解,统计每个包含质因子x的数有多少个,然后在进行容斥原理,奇加偶减。

My Code:

#include <iostream>
#include <cstring>
#include <cstdio>
using namespace std;
typedef long long LL;

int scan()///输入外挂
{
    int res=0,ch,flag=0;
    if((ch=getchar())=='-')
        flag=1;
    else if(ch>='0'&&ch<='9')
        res=ch-'0';
    while((ch=getchar())>='0'&&ch<='9')
        res=res*10+ch-'0';
    return flag?-res:res;

}

inline void out(LL a)///输出外挂
{
    if(a>9)
        out(a/10);
    putchar(a%10+'0');
}

const int MAXN = 2e5+5;

int p[MAXN];
bool prime[MAXN];
int k;
void isprime()
{
    memset(prime, false, sizeof(prime));
    k = 0;
    for(LL i=2; i<MAXN; i++)
    {
        if(!prime[i])
        {
            p[k++] = i;
            for(LL j=i*i; j<MAXN; j+=i)
                prime[j] = true;
        }
    }
}
int fac[MAXN/1000];
int cnt = 0;
void Dec(int m)
{
    cnt = 0;
    for(int i=0; p[i]*p[i]<=m&&i<k; i++)
    {
        if(m%p[i]==0)
        {
            fac[cnt] = p[i];
            while(m%p[i]==0)
                m /= p[i];
            cnt++;
        }
    }
    if(m > 1)
        fac[cnt++] = m;
}
int num[MAXN];
void Solve(int m)
{
    Dec(m);
    for(int i=1; i<(1<<cnt); i++)
    {
        int ans = 1;
        for(int j=0; j<cnt; j++)
            if(i & (1<<j))
                ans *= fac[j];
        num[ans]++;
    }
}
LL Get_ans(int m)
{
    Dec(m);
    LL ret = 0;
    for(int i=1; i<(1<<cnt); i++)
    {
        int sum = 0, ans = 1;
        for(int j=0; j<cnt; j++)
        {
            if(i & (1<<j))
            {
                sum++;
                ans *= fac[j];
            }
        }
        if(sum & 1)
            ret += num[ans];
        else
            ret -= num[ans];
    }
    ///cout<<ret<<endl;
    return ret;
}
LL Cal(int m)
{
    return (LL)m * (LL)(m-1) * (LL)(m-2) / 6;
}
int a[MAXN];
int main()
{
    ///cout<<Cal(66666);
    isprime();
    int T, m;
    T = scan();
    while(T--)
    {
        m = scan();
        memset(num, 0, sizeof(num));
        for(int i=0; i<m; i++)
        {
            a[i] = scan();
            Solve(a[i]);
        }
        LL ret = 0;
        for(int i=0; i<m; i++)
        {
            LL ans = Get_ans(a[i]);
            if(!ans)
                continue;
            ret += (ans-1)*(m-ans);
            ///cout<<ans<<endl;
        }
        ///cout<<ret<<endl;
        cout<<Cal(m)-ret/2<<endl;
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值