写在前面
需要学会的前置技能:
快速幂
一颗热爱学习的心
笔者是一名十八线蒟蒻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)=21⋅30⋅51=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)=32⋅50⋅71=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} 2≤x≤109,1≤n≤1018) — 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.597⋅10171. 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
pm≤n的最大整数
容斥原理
有了上述结论后,我们只要计算出每种答案出现的次数即可。
但可以注意到,当
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}
pm−1,它出现的次数并不是是
⌊
n
/
p
m
−
1
⌋
\lfloor n/p^{m-1}\rfloor
⌊n/pm−1⌋,而是要去掉之前
p
m
p^m
pm的次数
⌊
n
/
p
m
⌋
\lfloor n/p^m\rfloor
⌊n/pm⌋,
因此
p
m
−
1
p^{m-1}
pm−1出现的次数为
⌊
n
/
p
m
−
1
⌋
−
⌊
n
/
p
m
⌋
\lfloor n/p^{m-1}\rfloor-\lfloor n/p^{m}\rfloor
⌊n/pm−1⌋−⌊n/pm⌋。
同理
p
m
−
2
p^{m-2}
pm−2出现的次数为
⌊
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/pm−2⌋−⌊n/pm−1⌋−⌊n/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)=p∑i=1mi∗ai
有了
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