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?
* 1≤Bi≤Ai
* For each pair( l , r ) (1≤l≤r≤n) , gcd(bl,bl+1...br)≥2
Input
The first line is an integer T(1≤T≤10) 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 1≤n,Ai≤105
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
Sample Output
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数组本质可以说是莫比乌斯函数~上图看真相……
好了,不得瑟了。具体见代码(现场代码,略丑勿喷):
#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;
}