2017多校训练赛第二场 HDU 6053 TrickGCD(容斥原理/莫比乌斯反演)

TrickGCD

Time Limit: 5000/2500 MS (Java/Others)    Memory Limit: 262144/262144 K (Java/Others)
Total Submission(s): 142    Accepted Submission(s): 48

Problem Description

You are given an array A , and Zhu wants to know there are how many different array B satisfy the following conditions?

* 1BiAi
* For each pair( l , r ) (1lrn) , gcd(bl,bl+1...br)2


Input

The first line is an integer T(1T10) describe the number of test cases.

Each test case begins with an integer number n describe the size of array A.

Then a line contains n numbers describe each element of A

You can assume that 1n,Ai105


Output

For the kth test case , first output "Case #k: " , then output an integer as answer in a single line . because the answer may be large , so you are only need to output answermod109+7


Sample Input


1 4 4 4 4 4

Sample Output


Case #1: 17

Source

2017 Multi-University Training Contest - Team 2



        最后五分钟A了这题……惊天地泣鬼神啊……一波三折,真是刺激!

        最初的想法,由于gcd是大于2的,然后gcd肯定只和质数有关,所以直接枚举,以每一个质数作为gcd,然后分别求出每个质数作为gcd的方案数加起来即可,然后对于每一位,用a[i]/gcd,就可以得到该位置的可选方案数,然后每位乘起来即可。可是事实并没有那么简单,很容易发现,计算完质数2、3的方案数后,所有6的倍数的方案都被重复计算了一次,类似的重复还有很多。怎么解决这个问题呢?

        第一种想法就是,对于重复计算的,把它减去就行了。但是如何确定那些重复了呢?进一步我们发现,以质数2、3、5、7为例,6、10、14、15、21、35都被重复计算了一次,然后我们就要减去一次,但是减去的时候又发现30原本被计算了3次,现在又减去了2次……类似的情况难以统计,无法确定哪些数字重复统计了,因为在容斥一些元素后会对另外一些元素的计算次数产生影响。

        就这样挣扎了快四十分钟后,终于决定抛弃质数了。对于gcd,我枚举所有的数字,并且记录一个v数组,v[i]表示i的倍数被计算过多少次。从2开始枚举gcd,对于一个可能的gcd i,如果之前已经计算过1次,那么我们不用再计算;如果计算过v[i]次,那么我们就要对它相应的计算1-v[i]次。这样子的话就可以解决重复计算的问题。那么具体来说,如何统计它的倍数的方案数呢?我们设置一个s数组,s[i]表示an中,小于等于i的an的个数,这样子我们以统计gcd为3的时候为例,当循环到第一个倍数3时,方案数*1^(s[5]-s[3]),当到6时,方案数*2^(s[8]-s[6]),当循环到3*n时,方案数*2^(s[3*n+n-1]-s[3*n])。然后可能s[3*n+n-1]-s[3*n]表较大,所以用上快速幂。如此一来,每个gcd循环次数是n/gcd,求和之后就可以均摊地在O(log(N))的时间内求得每一个gcd的方案数。那么加上枚举gcd,总的时间复杂度就是O(NlogN)。

        其实呢,这种方法并不是正解,只是容斥原理的一种运用。后来看了题解发现,应该用莫比乌斯反演,然后我那个v数组其实和莫比乌斯反演中的g数组类似。所以可以说是当场YY出了个莫比乌斯反演?

        关于这个,我事后再补充一下,当我们把v数组减一输出后,我们会发现,v[i]恰好对应莫比乌斯函数的μ(i)。所以说,v数组本质可以说是莫比乌斯函数~上图看真相……


2017多校训练赛第二场 HDU 6053 TrickGCD(容斥原理/莫比乌斯反演)_第1张图片


        好了,不得瑟了。具体见代码(现场代码,略丑勿喷):

#include
#define mod 1000000007
#define LL long long
#define N 100100
using namespace std;

int a[N],s[N<<1],n,m;
int v[N];

LL fastpow(LL x,int n)						//快速幂
{
    LL ret=1;
    if (n<=0) return 1;
    while (n)
    {
        if (n&1) ret=ret*x%mod;
        x=x*x%mod; n>>=1;
    }
    return ret;
}

int main()
{
    m=0;
    int T_T;int T;
    scanf("%d",&T_T);T=T_T;
    while(T_T--)
    {
        scanf("%d",&n);
        memset(v,0,sizeof(v));
        memset(s,0,sizeof(s));
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&a[i]);
            s[a[i]]++;
        }
        sort(a+1,a+1+n);
        for(int i=1;i<=200000;i++) s[i]+=s[i-1];
        LL ans=0;
        for(int i=2;i<=a[1];i++)					//枚举gcd
        {
            if (v[i]!=1)						//如果恰好计算了一次,则不用再计算
            {
                int d=1-v[i];						//每次求d次方案数
                LL pans=1; int pre=1,pp=a[n]/i;
                for(int k=1;k<=pp;k++)
                {
                    int x=k*i;
                    v[x]+=d;						//倍数计算次数增加d次
                    pans=pans*fastpow(k,s[x+i-1]-s[pre-1]);		//计算部分的方案书p ans
                    if (pans>=mod) pans%=mod;
                    pre=x+i;
                }
                ans=ans+pans*d;						//对答案的贡献也要乘上d
                ans=(ans%mod+mod)%mod;
            }
        }
        printf("Case #%d: %I64d\n",T-T_T,ans);
    }
    return 0;
}

你可能感兴趣的:(---------Online,Judge--------,HDU,2017HDU多校赛,组合计数,容斥原理,欧拉/莫比乌斯)