HDU 5072 Coprime(2014 Asia AnShan Regional Contest C)

【题目链接】http://acm.hdu.edu.cn/showproblem.php?pid=5072

【解题报告】
题目大意为:给出N个互不相同的数字,试求有多少个这样的三元组,满足两两互质或两两不互质。
可以首先给出一个裸的枚举算法,即三重循环枚举三元组判断它是否满足题目要求,这样的复杂度是O(n^3),对于1e5的数据量来说显然是不可承受的。
于是可以考虑,两个数之间只有两种关系:互质||不互质,假如在两个数a,b之间连一条线表示它们两者之间的映射关系,红色线段表示两个数满足互质关系,蓝色表示不互质。那么题目可以转化为:

N个互不相同的数之间互相连线,求有多少个同色三角形。(三角形总数为C(n,3))

对于任意一个三角形,满足:如果不是同色三角形,那么至少会有两条边颜色不相同。考虑到此处只有两种颜色,那么当且仅当一个三角形中有两条异色线段(同时还有两条同色线段)时,他们不是同色三角形。
于是我们可以考虑,从一个点引出两条异色线段,那么不管另外两个点之间是什么关系,这样一个三角形一定是异色三角形。
于是题目转化为:

求有多少个异色三角形。

解决题目的核心为,要求异色三角形数,需要知道对于每个点,剩下的n-1个点和它是否互质。于是我们可以先试着解决这样一个问题:

给定N,求区间[1,size]之间与N互素的数的个数。

这里同样可以不加思考的给出一个枚举算法,两重循环遍历点集,统计出对于点Ai,和它互质的点的数目。时间复杂度为O(n^2)。对于1e5的数据量仍然是不可承受的。所以,如何高效的解决这个问题呢?
答:使用容斥原理。
下面给出的链接《容斥原理》里有详细的题解,这里不再赘述。
将这个问题稍加改动,就变成了我们需要解决的问题:

在给定的N个互不相同的数中,求与数K互素的数的个数。

因为不能直接由N/i(i是k的约数)得到N个数中以i为约数的数的个数,所以需要写一个函数来维护num[i],表示以i为约数的数的个数。

这一步的时间复杂度为O(N*cnt*2^cnt),cnt<10

之后可以得到答案:

ans=ALL-sum(P[i]*(n-1-P[i]))/2.

其中P[i]为第i个数与其他数之间互质的组数,由于一条边被统计了两次(两个端点分别计算一次),所以需要除以2.

到这里,这个题目就全部解决了。总的时间复杂度为O( NlogN+N*cnt*2^cnt )。还有不懂的地方可以参考代码及注释。

【参考文档】
《ACM竞赛中的逆向思维》–Mr.SoPhoebe
http://blog.csdn.net/u013007900/article/details/49493967
《容斥原理(译)》–vici
http://www.cppblog.com/vici/archive/2011/09/05/155103.html
《容斥原理题集》–zzxyyz_1
http://blog.csdn.net/zzxyyx_1/article/details/16864035

【参考代码】

#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
const int maxn=1e5+10;
int N;
int a[maxn], num[maxn], P[maxn][10];

void get_prime() //对于数j,找到它所有约数i。记录在P[j][k]中
{
    memset(P,0,sizeof P );
    for( int i=2; i<=maxn; i++ )if(!P[i][0])
    {
        for( int j=i; j<maxn; j+=i )  //一开始写成j<=maxn,RE,因为数组开的是[0,maxn-1]...
        {
            P[j][++P[j][0]]=i;  
        }
    }
}

void init()  //维护num数组,num[k]表示可以被k整除的数的个数
{
    memset(num,0,sizeof(num));
    for( int i=1; i<=N; i++ )
    {
        int cnt=P[a[i]][0]; 
        for( int j=1; j<(1<<cnt); j++ )
        {
            int temp=1;
            for( int k=0;k<cnt; k++ )if(  (1<<k)&j )
            {
                temp*=P[a[i]][k+1];
            }
            num[temp]++;//可以被temp整除的数+1
        }
    }
}

long long solve()
{
    long long  ans=(long long)N*(long long)(N-1)*(long long)(N-2)/6;
      long long tot_all=0;
    for( int i=1; i<=N; i++ )
    {
        int cnt=P[a[i]][0];
        int tot=0; //和a[i]不互质的数有tot个
        for( int j=1; j<(1<<cnt); j++ )
        {
            int item=0,temp=1;
            for( int k=0; k<cnt; k++ )if( (1<<k)&j )
            {
                temp*=P[a[i]][k+1];
                item++;
            }
            if(item&1)tot+=num[temp];
            else tot-=num[temp];
        }
        if(tot>0)tot--;  //减去a[i]本身
        tot_all+=(long long)(tot)*(long long)(N-1-tot);

    }
    return ans-tot_all/2;
}

int main()
{
    int T;
    cin>>T;
    get_prime();
    while(T--)
    {
        cin>>N;
        for( int i=1; i<=N; i++ )scanf("%d",&a[i]);
        init();
        printf("%lld\n",solve());
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值