【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个数运用试除法进行质因数分解可能超时,所以我们完全可以预先对于 1 105这些数进行质因数分解并存起来,需要的时候再拿出来用即可。至于空间开销和枚举素因子组合所需要的时间,我们可以证明 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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值