Codeforces Round #589 (Div. 2) C.Primes and Multiplication (数学推导+容斥原理+快速幂)

本文介绍了如何使用容斥原理和快速幂解决Codeforces Round #589 (Div. 2) C.Primes and Multiplication的问题。通过数学推导,作者分析了题目并给出了非最优解的思路,探讨了如何计算每个项的出现次数,并提供了31ms的代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

写在前面

需要学会的前置技能:
快速幂
一颗热爱学习的心

笔者是一名十八线蒟蒻ACMer ACMerACMer,如果文章有误,请在评论区下留言,我会尽快处理,非常感谢!
注:这个方法并不是最优的,只是笔者在当时想到的一种方法,关于最优方法,各位可以看看出题人自己给出的题解。比较简洁。
再次重申!本文描述的不是最优解!

原题体面

时间限制:1s
空间限制:256 MB
Let’s introduce some definitions that will be needed later.
Let p r i m e ( x ) prime(x) prime(x) be the set of prime divisors of x x x. For example, p r i m e ( 140 ) = ( 2 , 5 , 7 ) prime(140)=(2,5,7) prime(140)=(2,5,7), p r i m e ( 169 ) = ( 13 ) prime(169)=(13) prime(169)=(13).
Let g ( x , p ) g(x,p) g(x,p) be the maximum possible integer p k p^k pk where k k k is an integer such that x x x is divisible by p k p^k pk. For example:
g ( 45 , 3 ) = 9 g(45,3)=9 g(45,3)=9 ( 45 45 45 is divisible by 3 2 = 9 3^2=9 32=9 but not divisible by 3 3 = 27 3^3=27 33=27),
g ( 63 , 7 ) = 7 g(63,7)=7 g(63,7)=7 ( 63 63 63 is divisible by 7 1 = 7 7^1=7 71=7 but not divisible by 7 2 = 49 7^2=49 72=49).
Let f ( x , y ) f(x,y) f(x,y) be the product of g ( y , p ) g(y,p) g(y,p) for all p p p in p r i m e ( x ) prime(x) prime(x). For example:
f ( 30 , 70 ) = g ( 70 , 2 ) ⋅ g ( 70 , 3 ) ⋅ g ( 70 , 5 ) = 2 1 ⋅ 3 0 ⋅ 5 1 = 10 f(30,70)=g(70,2)⋅g(70,3)⋅g(70,5)=2^1⋅3^0⋅5^1=10 f(30,70)=g(70,2)g(70,3)g(70,5)=213051=10,
f ( 525 , 63 ) = g ( 63 , 3 ) ⋅ g ( 63 , 5 ) ⋅ g ( 63 , 7 ) = 3 2 ⋅ 5 0 ⋅ 7 1 = 63 f(525,63)=g(63,3)⋅g(63,5)⋅g(63,7)=3^2⋅5^0⋅7^1=63 f(525,63)=g(63,3)g(63,5)g(63,7)=325071=63.
You have integers x x x and n n n. Calculate f ( x , 1 ) ⋅ f ( x , 2 ) ⋅ … ⋅ f ( x , n ) m o d ( 1 0 9 + 7 ) f(x,1)⋅f(x,2)⋅…⋅f(x,n)mod(10^9+7) f(x,1)f(x,2)f(x,n)mod(109+7).

Input

The only line contains integers x x x and n n n ( 2 ≤ x ≤ 1 0 9 , 1 ≤ n ≤ 1 0 18 2≤x≤10^9, 1≤n≤10^{18} 2x109,1n1018) — the numbers used in formula.

Output

Print the answer.
Input1

10 2

output1

2

Input2

20190929 1605

output2

363165664

Input3

947 987654321987654321

output3

593574252

Note

In the first example, f ( 10 , 1 ) = g ( 1 , 2 ) ⋅ g ( 1 , 5 ) = 1 , f ( 10 , 2 ) = g ( 2 , 2 ) ⋅ g ( 2 , 5 ) = 2 f(10,1)=g(1,2)⋅g(1,5)=1, f(10,2)=g(2,2)⋅g(2,5)=2 f(10,1)=g(1,2)g(1,5)=1,f(10,2)=g(2,2)g(2,5)=2.
In the second example, actual value of formula is approximately 1.597 ⋅ 1 0 171 1.597⋅10^{171} 1.59710171. Make sure you print the answer modulo ( 1 0 9 + 7 ) (10^9+7) (109+7).
In the third example, be careful about overflow issue.

题面分析

f f f的计算过程又臭又长,直接计算必定超时,所以我们考虑去转化问题。
首先,注意到
f ( x , n ) = ∏ i = 1 k g ( n , p i ) f(x,n)=\prod_{i=1}^{k}g(n,p_i) f(x,n)=i=1kg(n,pi)( k k k为满足 p k < = m p^k<=m pk<=m的最大整数)
∏ i = 1 n f ( x , i ) \prod _{i=1}^{n}f(x,i) i=1nf(x,i)
= ∏ i = 1 k g ( 1 , p i ) ∏ i = 1 k g ( 2 , p i ) . . . ∏ i = 1 k g ( n , p i ) \prod_{i=1}^{k}g(1,p_i)\prod_{i=1}^{k}g(2,p_i)...\prod_{i=1}^{k}g(n,p_i) i=1kg(1,pi)i=1kg(2,pi)...i=1kg(n,pi)
我们枚举去枚举 p i p_i pi,可以得到
= ∏ i = 1 n g ( i , p 1 ) ∏ i = 1 n g ( i , p 2 ) . . . ∏ i = 1 n g ( i , p k ) \prod_{i=1}^{n}g(i,p_1)\prod_{i=1}^{n}g(i,p_2)...\prod_{i=1}^{n}g(i,p_k) i=1ng(i,p1)i=1ng(i,p2)...i=1ng(i,pk)
然后,根据 g g g的定义注意到
g ( x , p ) ∗ p = g ( x p , p ) g(x,p)*p=g(xp,p) g(x,p)p=g(xp,p)---------①
g ( x , x ) = x g(x,x)=x g(x,x)=x--------②
结合①②可以得到
g ( x n , x ) = x n g(x^n,x)=x^n g(xn,x)=xn----------③
稍加推导也可以得到
g ( k x n , x ) = x n ( k   m o d   x ! = 0 ) g(kx^n,x)=x^n(k\ mod\ x!=0) g(kxn,x)=xn(k mod x!=0)---------④
因此,我们得到一个初步的结论:
对于一个 g ( i , p ) g(i,p) g(i,p),我们有
g ( i , p ) = { 1 ( i   m o d   p   ≠ 0 ) p i = k p ( k   m o d   p   ≠ 0 ) p 2 i = k p 2 ( k   m o d   p   ≠ 0 ) . . . p m i = k p m ( k   m o d   p   ≠ 0 ) g(i,p)= \begin{cases} 1& (i\ mod\ p\ \neq 0)\\ p& i=kp(k\ mod\ p\ \neq0)\\ p^2& i=kp^{2}(k\ mod\ p\ \neq 0)\\ ...\\ p^m& i=kp^m(k\ mod\ p\ \neq 0) \end{cases} g(i,p)=1pp2...pm(i mod p =0)i=kp(k mod p =0)i=kp2(k mod p =0)i=kpm(k mod p =0)
其中 m m m是满足 p m ≤ n p^m\leq n pmn的最大整数

容斥原理

有了上述结论后,我们只要计算出每种答案出现的次数即可。
但可以注意到,当 g ( i , p ) = p g(i,p)=p g(i,p)=p时, i i i出现的次数并不是 n / p n/p n/p这么简单,
因为 n / p n/p n/p是当 g ( i , p ) ≠ 1 g(i,p)\neq 1 g(i,p)=1的所有情况总和。
因此我们考虑用容斥原理。
先计算 p m p^m pm,它作为最大项,没有干扰,因此 p m p^m pm出现的次数就是 ⌊ n / p m ⌋ \lfloor n/p^m\rfloor n/pm
对于 p m − 1 p^{m-1} pm1,它出现的次数并不是是 ⌊ n / p m − 1 ⌋ \lfloor n/p^{m-1}\rfloor n/pm1,而是要去掉之前 p m p^m pm的次数 ⌊ n / p m ⌋ \lfloor n/p^m\rfloor n/pm,
因此 p m − 1 p^{m-1} pm1出现的次数为 ⌊ n / p m − 1 ⌋ − ⌊ n / p m ⌋ \lfloor n/p^{m-1}\rfloor-\lfloor n/p^{m}\rfloor n/pm1n/pm
同理 p m − 2 p^{m-2} pm2出现的次数为 ⌊ n / p m − 2 ⌋ − ⌊ n / p m − 1 ⌋ − ⌊ n / p m ⌋ \lfloor n/p^{m-2}\rfloor -\lfloor n/p^{m-1}\rfloor -\lfloor n/p^m\rfloor n/pm2n/pm1n/pm
同理得到其他的答案出现次数。
a i a_i ai p i p_i pi的出现次数,则
∏ i = 1 n g ( i , p ) = p ∑ i = 1 m i ∗ a i \prod_{i=1}^{n}g(i,p)=p^{\sum_{i=1}^{m}i*a_i} i=1ng(i,p)=pi=1miai
有了 g ( i , p ) g(i,p) g(i,p)之积,答案自然就出来了。
其他的细节请查看代码。总体复杂度未知,初步估计是非常小的。

代码实现(31ms)

//代码很丑,大家将就着看吧......
#include<bits/stdc++.h>
using namespace std;
const int maxn=5e5;
/*打一个5e5内的质数表。
因为在数学上可以证明,把n按质因子分解时,至多有一个质因子大于sqrt(n),
因此我们打出sqrt(n)内质数表来分解,剩下分不掉的就是一个质数*/
const long long mod=1e9+7;
bool number[maxn+5];
int prime[maxn+5];
int c=0;//质数总个数
long long x,n;
long long qsm(long long a,long long b,long long c)//快速幂
{
    long long ans=1,base=a;
    while(b!=0)
	{
        if(b&1)
        ans=ans*base%c;
        base=base*base%c;
        b>>=1;
	}
    return ans%c;
}
long long kk(long long a,long long b)//快速乘
{
    long long ans=1,base=a;
    while(b!=0)
	{
        if(b&1)
        ans=ans*base;
        base=base*base;
        b>>=1;
	}
    return ans;
}
long long f(long long p)//计算p的次方数
{
	long long maxp=0,cc=n;//maxp为最大的满足p^m<=n的m
	while(cc>=p)
	{
		maxp++;
		cc/=p;
	}
	long long cs[100];
	cs[maxp]=n/kk(p,maxp);
	long long zx=0;
	for(long long i=maxp-1;i>=1;i--)//倒着算
	{
		zx=(zx+cs[i+1]);//统计之前的次数
		cs[i]=(n/kk(p,i)-zx);//筛去之前有的次数
	}
	long long ans=0;
	for(long long i=1;i<=maxp;i++)
		ans=(ans+(i*cs[i]));//根据各自对答案的贡献计算最后的总次数
	return ans;
	//需要注意的是,这里的总次数应该是小于n本身的,不用担心超出long long 范围,所以可以不用欧拉降幂。
}
void init()//欧线性拉筛
{
    int i,j;
    memset(number,true,sizeof(number));
    memset(prime,-1,sizeof(prime));
    for(i=2;i<=maxn;i++)
    {
        if(number[i])
            prime[c++]=i;
        for(j=0;j<c&&prime[j]*i<=maxn;j++)
        {
            number[prime[j]*i]=false;
            if(i%prime[j]==0) //保证每个合数只会被它的最小质因数筛去,因此每个数只会被标记一次
                break;
        }
    }
}
int main()
{
	init();
	scanf("%lld%lld",&x,&n);
	long long yz[10000];//x的质因子
	int psum=0;
	for(int i=0;i<c && prime[i]<=x && x>1;i++)//筛因子 
		{
			if(x%prime[i]==0 && && x>1)
			{
				yz[++psum]=prime[i];
				while(x>=prime[i] && x%prime[i]==0)
				{
					x/=prime[i];
				}
			}
		}
	if(x>1)//剩下一个大于sqrt(x)的数,必是质数
	{
		yz[++psum]=x;
	}
	long long ans=1;
	for(int i=1;i<=psum;i++)
	{
		ans=(ans*qsm(yz[i],f(yz[i]),mod))%mod;//统计求和
	}
	printf("%lld\n",ans%mod);
}

后记

因为一些事情,没能赶上这场比赛。于是后半夜开始补题。
在构筑思路,到代码实现,最后不断改BUG后AC的过程,虽然很长,但真的是很棒的体验。
凌晨三点,在室友都早已深深睡去的时候,屏幕上跳出的“Accept”是属于我一个人的小确幸。
不过,出题人给出的思路似乎复杂度比我的要小,但听群里大佬说可能需要unsigned long long之类的…
今年是笔者打ACM的第一年,希望一切安好,希望自己和队友在接下来的比赛可以拿个牌安享晚年
DrGilbert 2019.9.30

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值