TrickGCD
Time Limit: 5000/2500 MS (Java/Others) Memory Limit: 262144/262144 K (Java/Others)
Total Submission(s): 746 Accepted Submission(s): 293
* 1≤Bi≤Ai
* For each pair( l , r ) ( 1≤l≤r≤n ) , gcd(bl,bl+1...br)≥2
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
1 4 4 4 4 4
Case #1: 17
题意:
给你一个数组A,求一个数组B,数组B中的元素满足两个条件。
1. 1<=bi<=ai
2. 数组B中任意bi~br,其gcd>=2
求所有符合上述条件的B数组的种类数。
思路:
我们先找出数组A中最小的数amin,然后从1遍历到amin,我们用a[j]/i找出A中所有元素含有多少个这个因子,然后将A中个元素所得种类数相乘,再把从1到amin中的所有种类数相加。最后因为有重复因子出现,所以要考虑去重的情况。下面用例子仔细说明这个思路。
1
4
4 5 6 8
amin=4,我们就从2遍历到4。
i=2时,4里面有2个2,代表取种类的时候可以取2,4,(4/2=2),5里面有2个2,代表取种类的时候可以取2,4,(5/2=2),6里面有3个2,代表取种类的时候可以取2,4,6,(6/2=3),8里面有4个2,代表取种类的时候可以取2,4,6,8,(8/2=4).然后所有i=2时我们得出的结果就应该是2*2*3*4=48,即4可以取2种,5可以取2种…所以总种数是相乘。然后显然,我们应该把i=3,i=4的情况加起来。但是我们看i=4的情况,会出现4,4,4,4这一种情况,而在i=2时,也会出现4,4,4,4这一种情况,所以这就出现一种重复,我们就需要去重。这一题我们可以借助莫比乌斯函数来实现如何去重,下面介绍为什么要使用莫比乌斯。
为什么使用莫比乌斯函数?
我们把i从2遍历到amin,所有的i共有4种情况。
1. i本身是一个质数
2. i可以分解成奇数个不同的质数相乘
3. i可以分解成偶数个不同的质数相乘
4. i可以分解成有相同质数相乘的形式
我们解题的总体思路是讨论i的所有因子,表示在遍历i的这些因子时,我们都考虑过i,对遍历的结果都贡献了“1”,但我们考虑的最后结果是,必须要保证所有的值都只考虑了一次,所以我们就要讨论对重复考虑的情况进行去重。比如i=6,在i=2,i=3,i=6的时候都考虑过6的这种情况,这就会产生重复,6重复考虑了3次,下面介绍如何解决这种重复。以i的四种情况分别讨论去重的情况。我们记a[i]为遍历到i时,考虑去重后所得的系数。
先说结论再证明:
1. a[i]=1 2. a[i]=1 3. a[i]=1 4. a[i]=0
证明如下:
1. i本身是一个质数,所以他的因子(从2开始)只有他自己,从2遍历,i只出现了一次,所以a[i]=1.
2. 若i可分解成奇数个不同的质数相乘。例如30,可分解成2*3*5=30。30的因子为2 3 5 6 10 15 30。其中2,3,5都只出现了一次,次数分别+1,到6时,我们发现6=2*3,10=2*5,15=3*5,这三种情况都为分解成偶数个不同质数相乘的情况(具体参考情况3),所以6,10,15都-1,到30时,30在前面所有的因子遍历中出现了0次,所以应该+1才能保证出现了30出现了一次。将这个情况做一个推广。若i可分解成n个不同的质数相乘,从2一直到i的所有因子,其个数应该为C(n,1)+C(n,2)+…+C(n,n-1)+C(n,n),其中C(n,1)为所有的质因子个数,C(n,2)应为所有两两质因子的组合所得出的因子个数,以此类推。所以根据奇数个质因子相乘+1,偶数个质因子相乘-1的情况,可以得出C(n,1)-C(n,2)+C(n,3)-C(n,4)+…-C(n,n-1)(n为奇数)+a[i]的系数=1,由于C(n,m)=C(n,n-m)可得C(n,1)-C(n,2)+C(n,3)-C(n,4)+…-C(n,n-1)(n为奇数)等于0,故a[i]=1.
3. 证明过程类似于2.只不过n为偶数,式子可写成C(n,1)-C(n,2)+C(n,3)-C(n,4)+…+C(n,n-1)(n为偶数)+a[i]=1,由计算可得C(n,1)-C(n,2)+C(n,3)-C(n,4)+…+C(n,n-1)(n为偶数)=2*(C(n,1)-C(n,2)+…(-1)^n/2 * C(n,n/2-1))+(-1)^n/2+1 * C(n,n/2),再由组合数的性质,C(n,k)=C(n-1,k-1)+C(n-1,k)可得,此式为2,所以a[i]的系数为-1.
4. 我们将这种情况分为两种情况介绍。
<1>. i是平方数。(例如4,16,25)首先介绍一个定义,一个数m,若m的因子中含有平方数(4,9,16…),我们就称m为含有平方因子的数。介绍一个结论,从n到n²中,所有n²的因子都是含有平方因子的数,所以按照含有平方因子的系数都为0的情况,我们只需要考虑从2到n就可以。而此时n可以再当作i做判断,再循环到1.2.3.4任意一种情况再做处理,处理之后的n,一定是只出现过一次的情况,(即此时,从2到n,实现了n²只出现了一次),而根据n到n²的所有a的值都为0,故若保证最后结果为1,必使a[n²]=0。(从n到n²的每一个含有平方因子的数,都可以再次进去4进行循环,可得a[m]=0)。
<2>. i不是平方数。(例如12,18,32)我们可以把它分解成有相同质数相乘的形式,例如12可以分解成2*2*3.由于出现重复的因子,我们可以先把他当作一个因子来看待,找出其与i的因子的区别。例如12,若我们根据2,3来找出12的因子,应该为2,3,6.而实际12的因子还有4,12.对比我们发现少的因子全部为含有平方数的因子。这个结论并不特殊。因为我们删除重复因子后,就相当于删除了两个(或多个)重复因子相乘的因子,例如删除了2后,我们也就删除了2*2=4这个因子,删除了2*2*3=12这个因子,也就是说,我们删除了所有能分解成两个或多个相同因子相乘的i的因子,而根据4,这些因子的结果都为0。(这些因子再进到4里面进行循环,最后得出结果为0)所以我们可以不再考虑这些因子,再将剩下的因子再次判断1.2.3.4进行循环,根据递推逐次推导,得出最后i的结果。(相当于从大到小遍历i的一部分因子)。如a[12]=0.
由此,此题结论全部证明完毕。下面介绍莫比乌斯函数。
u(n)=1,n=1
u(n)=(-1)^k,n=p1*p2*…pk (p为不同的素因子)
u(n)=0,其余情况
我们分析莫比乌斯函数,发现恰好u[i]=-a[i],(其证明过程在某些方面确与莫比乌斯函数相似)。所以此题可以用莫比乌斯函数实现去重的过程。
公式:
根据上述过程,我们可以得出此题的公式为:[i从2到amin遍历求和][-u(i)*(j从1到n遍历求积)(a[j]/i)]
然后我们对这个公式进行优化,我们把a[j]/i相同的放在一起,求遍历i时每一区域内的因子数,然后用快速幂降低时间复杂度,优化后的式子为:[i从2到amin遍历求和][-u(i)*(j从1到j*i<=100000)(j^num[j*i+i-1]-num[j*i-1])
下面贴上代码:
#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
#define ll long long
const int maxn=200000;
const int mod=1e9+7;
ll a;
ll mu[maxn+10];
ll num[maxn+10];
bool check[maxn+10];
ll prime[maxn+10];
void mobius()
{
memset(check,0,sizeof(check));
mu[1]=1;
ll tot=0;
for(ll i=2; i<maxn; i++)
{
if(!check[i]) //check[i]代表i是否是素数,check[i]=1代表i不是素数
{
prime[tot++]=i; //prime是素数集 prime[0]=2
mu[i]=-1; //mu[i]代表莫比乌斯函数u[i]
}
for(int j=0; j<tot; j++)
{
if(i*prime[j]>maxn)
break;
check[i*prime[j]]=true;
if(i%prime[j]==0)
{
mu[i*prime[j]]=0;
break;
}
else
mu[i*prime[j]]=-mu[i];
}
}
}
ll quickpow(ll x,ll y) //ans=x^y
{
ll ans=1;
while(y)
{
if(y%2==1)
ans=ans*x%mod;
x=(x*x)%mod;
y=y>>1;
}
ans=ans%mod;
return ans;
}
int main()
{
mobius();
int T;
scanf("%d",&T);
int cas=1;
while (T--)
{
memset(num,0,sizeof(num));
int n;
scanf("%d",&n);
ll amin=maxn;
for(int i=1; i<=n; i++)
{
scanf("%lld",&a);
amin=min(amin,a); //amin存储A数组中的最小值
num[a]++;
}
for(int i=1; i<maxn; i++)
num[i]+=num[i-1]; //num[i]表示数组A中小于等于i的数的个数
ll res=0;
for(ll i=2; i<=amin; i++)
{
ll temp=1;
for(ll j=1; j*i<=100000LL; j++)
temp=(temp*quickpow(j,num[j*i+i-1]-num[j*i-1])%mod)%mod;
res=(res-temp*mu[i]%mod+mod)%mod;
}
printf("Case #%d: %lld\n",cas++,res);
}
return 0;
}