【题目链接】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;
}