2018.06.29 NOIP模拟 Gcd(容斥原理)

Gcd

题目背景

SOURCE:NOIP2015-SHY-2

题目描述

给出n个正整数,放入数组 a 里。

问有多少组方案,使得我从 n 个数里取出一个子集,这个子集的 gcd 不为 1 ,然后我再从剩下的数中取出一个数,把他放进刚刚取出的子集里,使得 gcd 为 1 。

输出方案数 mod (10^9 + 7)。

输入格式

第一行一个数 n 。

第二行 n 个数,表示 a 数组。

输出格式

输出一个数表示答案。

样例数据 1

输入

3

2 3 2

输出

5

备注

【样例说明】

set :2 number:3 count:2

set :3 number:2 count:2

set :2 2 number:3 count:1

total = 2 + 2 + 1 = 5

【数据范围】

对 30% 的输入数据 :1≤n≤10;

对 100% 的输入数据 :1≤n≤500000;2≤a中元素≤10000000 。

这是我做过的最可(du" role="presentation" style="position: relative;">dudu)做(liu" role="presentation" style="position: relative;">liuliu)的NOIP" role="presentation" style="position: relative;">NOIPNOIP模拟级别的数论题。考场上打算写随机算法,想了想30" role="presentation" style="position: relative;">3030暴力更稳,于是交了暴力,结果10" role="presentation" style="position: relative;">1010分滚粗了。

这题让我们联想到正难则反的思想,题目上要我们求出所有可能解的方案数,那么我们这样想,我们先把总方案数求出来,显然是n∗(2n−1−1)" role="presentation" style="position: relative;">n∗(2n−1−1)n∗(2n−1−1)。

然后我们将不合法的情况去掉,我们设加入的数为number" role="presentation" style="position: relative;">numbernumber,那么我们要去掉的就是在加入number" role="presentation" style="position: relative;">numbernumber之前就已经使得gcd" role="presentation" style="position: relative;">gcdgcd为1" role="presentation" style="position: relative;">11的集合对应的number" role="presentation" style="position: relative;">numbernumber的选择方案数之和,以及在加入number" role="presentation" style="position: relative;">numbernumber之后gcd" role="presentation" style="position: relative;">gcdgcd仍然不为1" role="presentation" style="position: relative;">11的方案数。这东西怎么算啊?变形代数式看看吧。

首先解决第二种情况,即在加入number" role="presentation" style="position: relative;">numbernumber之后gcd" role="presentation" style="position: relative;">gcdgcd仍然不为1" role="presentation" style="position: relative;">11的方案数。这种情况直接统计所有集合中使得gcd" role="presentation" style="position: relative;">gcdgcd不为一的方案数即可,即用总方案数减去所有集合中使得gcd" role="presentation" style="position: relative;">gcdgcd为1" role="presentation" style="position: relative;">11的方案数。

另外,我们还知道,情况二其实是让我们求这个东西:∑|S|,gcd(S)!=1|S|" role="presentation" style="position: relative;">∑|S|,gcd(S)!=1|S|∑|S|,gcd(S)!=1|S|

这样的话,我们似乎将第二种情况转化成了第一种情况的子问题。怎么求出所有集合中使得gcd" role="presentation" style="position: relative;">gcdgcd为1" role="presentation" style="position: relative;">11的方案数呢?

考虑容斥原理,我们先将gcd" role="presentation" style="position: relative;">gcdgcd为1" role="presentation" style="position: relative;">11的倍数的集合数求出,减去gcd" role="presentation" style="position: relative;">gcdgcd为2" role="presentation" style="position: relative;">22的倍数的集合数……一直这样迭代下去,然后有个显然的结论,每个gcd" role="presentation" style="position: relative;">gcdgcd的容斥系数就对应着它们的莫比乌斯函数值。于是只需要提前用线性筛筛出所有数的莫比乌斯函数值,每次将情况二的答案加上mu[i]∗(2sum[i]−1)" role="presentation" style="position: relative;">mu[i]∗(2sum[i]−1)mu[i]∗(2sum[i]−1)就行了,其中sum[i]" role="presentation" style="position: relative;">sum[i]sum[i]表示所有数列中所有是i" role="presentation" style="position: relative;">ii的倍数的数的个数。

情况一?考虑用情况二反推情况一,再次将式子变形:我们要求的是在加入number" role="presentation" style="position: relative;">numbernumber之前就已经使得gcd" role="presentation" style="position: relative;">gcdgcd为1" role="presentation" style="position: relative;">11的集合对应的number" role="presentation" style="position: relative;">numbernumber的选择方案数之和,即∑S,gcd(S)==1(n−|S|)" role="presentation" style="position: relative;">∑S,gcd(S)==1(n−|S|)∑S,gcd(S)==1(n−|S|),显然这个式子可以展开成∑S,(gcd)==11−∑S,gcd(S)==1|S|" role="presentation" style="position: relative;">∑S,(gcd)==11−∑S,gcd(S)==1|S|∑S,(gcd)==11−∑S,gcd(S)==1|S|,哇,后面一坨不是可以跟情况二合并吗?好像还可以抵掉一坨东西。最后发现只需要容斥计算乱搞一下就行了啊。

代码如下:

#include<bits/stdc++.h>
#define inf 10000005
#define mod 1000000007
#define ll long long
using namespace std;
int n,tot=0,mu[inf],prime[inf],cnt[inf],sum[inf];
ll mul[inf],a=0,b=0;
bool pr[inf];
inline ll read(){
    ll ans=0;
    char ch=getchar();
    while(!isdigit(ch))ch=getchar();
    while(isdigit(ch))ans=(ans<<3)+(ans<<1)+ch-'0',ch=getchar();
    return ans;
}
int main(){
    n=read();
    for(int i=1;i<=n;++i){
        int x=read();
        ++cnt[x];
    }
    mul[0]=1;
    for(int i=1;i<=n;++i)mul[i]=mul[i-1]<<1,mul[i]%=mod;
    mu[1]=1;
    for(int i=2;i<=inf;++i){
        if(!pr[i])prime[++tot]=i,mu[i]=-1;
        for(int j=1;j<=tot&&i*prime[j]<=inf;++j){
            int k=i*prime[j];
            pr[k]=1;
            if(i%prime[j]==0){
                mu[k]=0;
                break;
            }
            mu[k]=-mu[i];
        }
    }
    for(int i=1;i<=inf;++i)
        for(int j=i;j<=inf;j+=i)sum[i]+=cnt[j];
    for(int i=1;i<=inf;++i)
        if(mu[i]&&sum[i]){
            a=(a+mu[i]*mul[sum[i]]-mu[i])%mod;
            b=(b+mu[i]*mul[sum[i]-1]*sum[i])%mod;
        }
    a=a*n%mod;
    b=(b<<1)%mod;
    a=(b-a+mod)%mod;
    printf("%lld",a);
    return 0;
}
上一篇:awk分隔符设定为多个字符或字符串


下一篇:JS数组常用函数以及查找数组中是否有重复元素的三种常用方法