Sky Code poj-3904

    题目大意:给你n个数,问能选出多少满足题意的组数。

    注释:如果一个组数满足题意当且仅当这个组中有且只有4个数,且这4个数的最大公约数是1,$1\le n\le 10^4$。

      想法:我们显然可以知道4个数是可以不用两两互质的,所以正面计算难度较大,我们考虑从反面考虑。我们通过计算所有gcd不为1的组数,用总组数相减即可。然后,我们发现一个不为0的gcd显然可以被组中的任意一个数整除,所以我们可以进行容斥。只需要枚举gcd的约数个即可。计算的过程我们用状态压缩实现。

    最后,附上丑陋的代码... ...

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define maxn 10005 
using namespace std;
typedef long long ll;
ll a[10010];//记录单个数的质因数
ll cnt;//记录单个数的质因数个数
ll ans[10010][2];//ans[i][0]表示包含i这个因子的数的个数,ans[i][1]表示i的质因子个数
ll Calc(ll x)//计算C[n][4]
{
	return x*(x-1)*(x-2)*(x-3)/24;
}
void separate(ll x)//分解质因数,由于我们在后面需要用cnt进行状态压缩,所以a数组从0开始记录
{
	for(int i=2;i*i<=x;i++)
	{
		if(x%i==0)
		{
			a[cnt]=i;
			cnt++;
			while(x%i==0)
			{
				x/=i;
			}
		}
	}
	if(x>1) a[cnt++]=x;
}
void dispose(ll x)
{
	cnt=0;
	separate(x);
	for(int i=1;i<(1<<cnt);i++)//通过枚举当前全集来统计桶
	{
		ll flag=0,middle=1;
		for(int j=0;j<cnt;j++)
		{
			if(i&(1<<j))
			{
				flag++;
				middle*=a[j];
			}
		}
		ans[middle][0]++;
		ans[middle][1]=flag;
	}
}
int main()
{
	ll n;
	while(~scanf("%lld",&n))
	{
		memset(ans,0,sizeof ans);
		ll x;
		for(int i=1;i<=n;i++)
		{
			scanf("%lld",&x);
			dispose(x);
		}
		ll answer=Calc(n);
		for(int i=2;i<=maxn/4;i++)
		{
			if(ans[i][0])//Important
			{
				if(1&ans[i][1]) answer-=Calc(ans[i][0]);//如果是偶数个质因子
				else answer+=Calc(ans[i][0]);//如果是奇数个质因子
			}
		}
		// puts("***");
		printf("%lld\n",answer);//输出答案即可
	}
	return 0;
}

     小结:如果一个问题极其复杂,我们不妨反其道而行之。容斥原理就是一例。