队内训练2 牛客多校第二场补题J 拓展欧拉定理
题目链接link.
题意:给你一个大小为s的数组,一个k一个p。问你,所有的大小为k的数组的gcd乘积之和。
这题,有人说用莫比乌斯做。本来想趁机学一手,但是网上能找到的这题的博客太少了,就还是先写个常规做法吧。等以后有机会学了莫比乌斯再来更另一种做法。特别鸣谢:池池&池池的学弟(队友)。
首先考虑贡献怎么算。可以先预处理出来1e7内所有的素数,然后枚举素数以及素数的次方,计算贡献。具体的贡献怎么算呢?当前枚举数的倍数的个数通通加起来。然后组合数在里面取k个就好啦。答案直接在原先的基础上乘以该素数的组合数次方就好啦。注意得用欧拉降幂和快速幂优化一下。
另外这题卡快读,我自己原先打cf用的快读板子是卡不过去的,会t掉20%的点,用池池学弟的快读板子就可以过啦。细节都在代码里注释了,这题的贡献算法其实不是很好描述,看代码自己写两个数跟着走一遍应该还是很好懂的。
卡常是坏文明,建议埋了
AC代码:
#include<iostream>
#include<cstring>
#include<cmath>
#define ll long long
using namespace std;
const ll N=1e7+8;
int primes[N+8], cnt;
bool st[N+8];
ll c[40008][31];
ll a[40005];
int ok[80005];
ll euler;
inline int read()
{
int x=0,f=1;
char ch=getchar();
while(ch<'0'||ch>'9')
{
if(ch=='-')
f=-1;
ch=getchar();
}
while(ch>='0' && ch<='9')
x=x*10+ch-'0',ch=getchar();
return x*f;
}
inline ll lread()
{
ll x=0,f=1;
char ch=getchar();
while(ch<'0'||ch>'9')
{
if(ch=='-')
f=-1;
ch=getchar();
}
while(ch>='0' && ch<='9')
x=x*10+ch-'0',ch=getchar();
return x*f;
}
void init_primes()//预处理素数
{
for (ll i=2;i<=N;i++)
{
if (!st[i]) primes[cnt++]=i;
//st等于0表示不能被之前那些数乘积表示,自然是素数
for (ll j=0;primes[j]<=N/i;j++)
//对于之前找到的所有的素数,
//N以内所有的能被当前数字i影响的
{
st[primes[j]*i]=true;
if(i%primes[j]==0) break;
//如果i能被primes[j]整除,
//说明primes[j]往后的影响已经包括了i的影响,
//所以可以直接break
}
}
return ;
}
ll phi(ll x)//求欧拉函数
//即小于等于m的且与m互质的正整数的个数
{
__int128 ans=x;
for (int i=0;primes[i]<=x/primes[i];i++)
//枚举sqrt(x)内所有的素数primes[i]
{
if (x%primes[i]==0)
//说明这个素数跟x不互质,比如说,x=12,primes[i]=2
//那么ans就要变成6组*(2-1)=6个,说明筛掉这个素数还剩下6个
//相当于容斥一下 12*(1-1/2)*(1-1/3)=4
{
ans=ans/primes[i]*(primes[i]-1);
while(x%primes[i]==0) x/=primes[i];
}
}
if (x>1) ans=ans/x*(x-1);//针对x本来就是素数的情况
return ans;
}
ll qmi(ll m,ll k,ll p)
//其实是快速幂
{
__int128 ans=1%p,t=m;
while(k)
{
if(k&1) ans=ans*t%p;
t=(__int128)t*t%p;
k>>=1;//k=k/2
}
return ans;
}
int main()
{
init_primes();
int t;
t=read();
while(t--)
{
int n,k;
ll mod;
n=read();k=read();
mod=lread();
memset(ok,0,sizeof ok);
ll maxx=0;
euler=phi(mod);
//递推求组合数,可以做到O(1)查询
c[0][0]=1;
for(int i=1;i<=n;i++)
{
c[i][0]=1;
for(int j=1;j<=min(i,k);j++)
{
c[i][j]=c[i-1][j-1]+c[i-1][j];
//就是杨辉三角那个性质
if(c[i][j]>=euler)
{
c[i][j]=c[i][j]%euler+euler;
//这波属于是扩展欧拉定理的预处理了
}
}
}
for(int i=1;i<=n;i++)
{
a[i]=lread();
ok[a[i]]++;
maxx=max(maxx,a[i]);
}
ll ans=1;
for(int i=0;primes[i]<=maxx;i++)
{
for(int j=primes[i];j<=maxx;j*=primes[i])
{
ll s=0;
for(ll pp=j;pp<=maxx;pp+=j) s+=ok[pp];
if(s<k) break;
ans=(__int128)ans*qmi(primes[i],c[s][k],mod)%mod;
}
}
printf("%lld\n",ans);
}
return 0;
}