题意:
给你一个长度为n的数组a,{
a1,a2,a3...an
},求一个长度也为的数组b{
b1,b2,b3...bn
}存在的种类数,b有两个要求,①
1<bi<ai;②数组b中任意子集的最小公因数大于1
题解:根据题意,b中数必须都是一个数的倍数.
首先遍历a数组找出其中最小的数amin,用k遍历从1到n中的数,看数组a中是否每个数都比k的倍数大,有则 种类数*
ai/k
所以种类数res为
res=∑k=1amin∏i=1na[i]k
但是这样结果会出现重复,比如15这个数,既出现在3的倍数中又出现在5的倍数中.
我们用简单的容斥定理排除掉重复的情况,也就是奇数加,偶数减
简单的用图解释一下
由图一来看A,B,C,出现一次,DEF出现两次,G出现3次。根据集合运算法则
|A∪B∪C|=|A|+|B|+|C|−|A∩B|−|B∩C|−|A∩C|+|A∩B∩C|
很容易就看出奇加偶减。
但是直接这样做会超时,所以要进行分段,以因数x分段,分段求累乘。
这里没有用莫比乌斯反演没用到,只用了莫比乌斯函数的数值。
莫比乌斯反演定理传送门
code:
#include<stdio.h>
#include<string.h>
#include<math.h>
#include<algorithm>
#include<iostream>
#include<string>
#include <set>
using namespace std;
#define ll long long
#define mem(a) memset(a,0,sizeof(a))
const int eps=1e-8;
const int maxn=200000;//须填写
const int inf=0x3f3f3f3f;
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()
{
mem(check);
mu[1]=1;
ll tot=0;
for(ll i=2;i<maxn;i++)
{
if(!check[i])
{
prime[tot++]=i;
mu[i]=-1;
}
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 kuai(ll x,ll 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 kase;
scanf("%d",&kase);
for(int ci=1;ci<=kase;ci++)
{
mem(num);
int n;
scanf("%d",&n);
ll mi=maxn;
for(int i=1;i<=n;i++)
{
scanf("%lld",&a);
num[a]++;
mi=a>mi?mi:a;
}
for(int i=1;i<maxn;i++)
num[i]+=num[i-1];
ll res=0;
//cout<<"!!!!!!!!!!!!!!!"<<endl;
for(ll i=2;i<=mi;i++)
{
ll temp=1;
for(ll j=1;j*i<=100000LL;j++)
{
temp=(temp*kuai(j,num[j*i+i-1]-num[j*i-1])%mod)%mod;
}
//cout<<"BBBB!!!!!!!!!!!!!!"<<endl;
res=(res-temp*mu[i]%mod+mod)%mod;
}
//cout<<"!!!!!!!!!!!!!!!"<<endl;
printf("Case #%d: %lld\n",ci,res);
}
return 0;
}