2021牛客多校第二场J

题意:给n个数,求所有从这n个数中选k个构成一组的gcd的乘积

思路:因为每个数都可以分解为质数的乘积,所以我们考虑枚举gcd就是枚举所有质数以及他的倍数的gcd;假设现在有一个质数p,要取k个数使他的gcd为p,则只可能是p的倍数(2p,3p……),假设n个数*有m个p的倍数,则贡献就是2021牛客多校第二场J;但是对于p²等p的幂次就相当于少算了(就是应该多乘几个p),所以把p的幂次也进行一次计算(实在不明白就找一个例子测试一下就行,反正我是想了很久还感觉迷迷糊糊的)

看不懂就借鉴这几篇文章吧:2021牛客多校 Product of GCDs(拓展欧拉定理+欧拉函数)_m0_51841071的博客-CSDN博客

队内训练2 牛客多校第二场补题J 拓展欧拉定理_ojbkoxy的博客-CSDN博客

总结一下:

1.枚举gcd,即枚举质数p以及它的所有幂次(当然要小于个数的最大值)

2.对于p或者他的幂次,计算这个数即他的倍数在n个数中出现的次数m

3.计算q=2021牛客多校第二场J,最后每次枚举的结果就是p^q

注意:

 1.k很大,用扩展欧拉降幂 (常数很大,你忍一下)

2.要用欧拉降幂,你又得知道欧拉函数

3.在求欧拉函数时,因为枚举常数会超时,所以你还需要枚举素数,所以你要求出所有素数

前置知识:

1.欧拉函数(f(x)代表小于等于x并与x互质的个数):

对于p^n(p为质数)来说:f(p^n)=(p-1)p^(n-1);

证明:对于p^n,与他不互质的数只有0,p,2*p……p^n-p,共有p^n/p个,所以与他互质的数就有p^n-p^n/p=(p-1)p^(n-1);

然后是一般情况下,欧拉函数的计算流程。其中用到了前两个性质。把n质因数分解,然后把各个质因子带入最终公式,计算欧拉函数值。(节选自百度百科)

证明过程如图:

2021牛客多校第二场J

就用这个就可以写出暴力求n的欧拉函数的函数  phi(n)(之前不知道为什么脑子没有绕过弯,死活想不明白):

ll phi(ll x)
{
    __int128 ans=x;//超范围
    for (int i=0;primes[i]<=x/primes[i];i++)//正常这里是枚举常数,但是因为超时,所以这里是枚举质数。。。离谱
//先除再乘,防止超范围
    {
        if (x%primes[i]==0)//prime[i]是x的质因数
        {
            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;
}

2.欧拉降幂:

欧拉定理、拓展欧拉定理及其应用(欧拉降幂法) - Reqaw - 博客园详细见此

2021牛客多校第二场J 3.最后快速幂就行

代码:

#include <bits/stdc++.h>
using namespace std;
const int N = 40010,M=10000010;
typedef long long ll;
int a[N];//真的怪,把这个数组改为a然后就超时,超时就算了,再改回来原来不超时的代码超时了!然后复制原来一模一样的代码交了,又能过了,真的离谱
//好了,我现在写个注释都会超时了
ll c[N][31];//计算组合数
int own[2*N];//计算每个数的次数
int prime[M],cnt;//根号P内的质数就行,因为我们是要求P的欧拉函数嘛
bool st[M];//是否为质数
int n, k;
ll P,euler;
//还要快读。。。这时间卡的
//大整数乘法:对于c=a?b的运算,由于a和b范围是10^14,直接乘会爆long long,
//因此可以用__int128做中间处理。

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 Isprime()//质数筛
{
	for (int i = 2; i < M; i++)
	{
		if (!st[i])
		{
			prime[cnt++] = i;
			for (int j = i * 2; j < M; j += i)
			{
				st[j] = true;
			}
		}
	}
}

ll phi(ll x)//欧拉函数
{
	__int128 ans = x;
	for (int i = 0; x / prime[i] >= prime[i]; i++)
	{
		if (x % prime[i] == 0)
		{
			ans = ans / prime[i] * (prime[i] - 1);
			while (x % prime[i] == 0)
			{
				x /= prime[i];
			}
		}
	}
	if (x > 1)
	{
		ans = ans / x * (x - 1);
	}
	return ans;
}

ll quick_mi(int p, ll m)//快速幂
{
	__int128 ans = 1, base = p;
	while (m)
	{
		if (m & 1)
		{
			ans = (ans * base) % P;
		}
		base = (base * base) % P;
		m >>= 1;
	}
	return ans%P;
}

int main()
{
	Isprime();
	int t;
	t = read();
	while (t--)
	{
		memset(own, 0, sizeof(own));//记得清零!!!!!!
		int Max = 0;
		n = read(), k = read(), P = lread();
		for (int i = 0; i < n; i++)
		{
			a[i]= read();
			own[a[i]]++;
			Max = max(a[i], Max);
		}

		euler = phi(P);
		c[0][0] = 1;
		for (int i = 1; i <= n; i++)//求组合数
		{
			c[i][0] = 1;
			for (int j = 1; j <= min(k, i); 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;//扩展定理
				}
			}
		}
		ll ans = 1;
		for (int i = 0; prime[i] <= Max; i++)
		{
			for (int j = prime[i]; j <= Max; j *= prime[i])
			{
				int m = 0;
				for (int q = j; q <= Max; q += j)
				{
					m += own[q];
				}
				if (m >= k)
				{
					ans = __int128(ans)* quick_mi(prime[i], c[m][k])%P;
				}
				else
				{
					break;//因为没退出wa了。。。。。 
				}
			}
		}
		printf("%lld\n",ans%P);
	}
	return 0;
}

欧拉公式延伸(这些没用到,但是了解一下挺好的,放最后吧):

一个数所有的质因数的和=f(n)*n/2

2021牛客多校第二场J

(后面那句话是解释来着。。。)妙啊!!!震惊

 wow,想明白了好神奇,反正就是这样

上一篇:统计素数并求和(两个判断素数的思路*)


下一篇:素数寻找算法