【HDU5072】Coprime-补集转化+容斥原理+质因数分解

测试地址:Coprime
题目大意:N个人,每个人有一个身份值id(i),每个人的身份值不同。现在要选出三个人去抵抗邪恶,选出的三个人之间必须要能沟通,三个人之间能沟通的充要条件是他们三人的身份值满足身份值两两互质或两两不互质。求选择的方案数。3N105,1id(i)105
做法:这一题需要利用补集转化思想进行分析,并使用容斥原理和状态压缩进行统计。
分析这一题的模型,我们可以把每个人看做一个点,每两个点之间有连边,如果两个点的身份值互质连红边,如果两个点的身份值不互质连黑边,那么问题就转化成求这样一个图中的同色三角形的数目。考虑到直接求比较麻烦,我们就利用补集转化思想,用总数减去异色三角形数目来得到答案。总数显然就是C3n=n(n1)(n2)/6,那么我们怎么来求异色三角形的数目呢?
画个图我们就会发现,一个异色三角形对应两对有公共顶点的异色边,那么我们可以求出有公共顶点的异色边对数,用这个对数除以2就可以得到异色三角形的数目。这个对数就非常好算了,设连接点i的红边数量为e(i),那么连接点i的黑边数量为n1e(i),那么公共顶点为点i的异色边对数就是e(i)×(n1e(i)),所以总的有公共顶点的异色边对数为Ni=1e(i)×(n1e(i))。那么如果我们能预处理出e(i),这个式子就是O(N)的了,已经非常好了,那么接下来我们考虑求e(i)
e(i)就是在所有id(j)(ji)中和id(i)互质的数的个数,那么我们暴力求肯定是会炸的,直接求又不好求,所以我们再次利用补集转化思想,用总数减去不与id(i)互质的数的个数来得到结果。我们将id(i)质因数分解,根据容斥原理,和id(i)不互质的数的个数为:和id(i)含有同1种素因子的数的个数-和id(i)含有同2种素因子的数的个数+…+(1)k1×id(i)含有同k种素因子的数的个数。求含有同几种素因子的数的数目就是求是这几个素因子乘积的倍数的数的数目。那么我们可以在开始预处理出在这些数中是某一个数i的倍数的数mul(i),至于怎么预处理,就是对于每一个id(j),枚举它的素因子组合,枚举使用状态压缩,然后对于每个素因子组合在mul()上加1。预处理出mul(i)之后,再对于每个id(i)计算不与它互质的数的个数,反过来求出与它互质的数的个数,这样就计算出了每个e(i),最后就可以统计答案了。
注意,由于有5组数据,在极端情况下,对5×105个数运用试除法进行质因数分解可能超时,所以我们完全可以预先对于1105这些数进行质因数分解并存起来,需要的时候再拿出来用即可。至于空间开销和枚举素因子组合所需要的时间,我们可以证明105以内的数不含有超过6种素因子(因为2×3×5×7×11×13×17>105),所以对于每一个数只需要用6个数存储素因子。这样的话,整个做法的时间复杂度应该是O(NN+26N),可以通过这道题。
以下是本人代码:

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
#define ll long long
using namespace std;
ll T,n,a[100010],fac[100010][7],mul[100010];
ll e[100010];

void find_factor(ll i)
{
  ll s=i;
  fac[i][0]=0;
  for(int j=2;j*j<=s;j++)
    if (!(s%j))
    {
      fac[i][++fac[i][0]]=j;
      while(!(s%j)) s/=j;
    }
  if (s>1) fac[i][++fac[i][0]]=s;
}

int main()
{
  scanf("%lld",&T);
  for(int i=1;i<=100000;i++)
    find_factor(i);

  while(T--)
  {
    memset(mul,0,sizeof(mul));
    memset(e,0,sizeof(e));
    scanf("%lld",&n);
    for(int i=1;i<=n;i++)
    {
      scanf("%lld",&a[i]);
      ll limit=(1<<fac[a[i]][0]);
      for(int j=1;j<limit;j++)
      {
        ll s=j,k=1,sum=1;
        while(s)
        {
          if (s&1) sum*=fac[a[i]][k];
          s>>=1;k++;
        }
        mul[sum]++;
      }
    }

    for(int i=1;i<=n;i++)
    {
      ll limit=(1<<fac[a[i]][0]);
      for(ll j=1;j<limit;j++)
      {
        ll s=j,k=1,sum=1,sign=-1;
        while(s)
        {
          if (s&1) sum*=fac[a[i]][k],sign*=-1;
          s>>=1;k++;
        }
        e[i]+=sign*mul[sum];
      }
      e[i]=n-e[i];
      if (a[i]==1) e[i]--;
    }

    ll ans=0;
    for(int i=1;i<=n;i++) ans+=e[i]*(n-1-e[i]);
    printf("%lld\n",n*(n-1)*(n-2)/6-ans/2);
  }

  return 0;
}
posted @ 2017-05-30 12:17  Maxwei_wzj  阅读(110)  评论(0编辑  收藏  举报