写在前面
需要学会的前置技能:
快速幂
一颗热爱学习的心
笔者是一名十八线蒟蒻ACMer ACMerACMer,如果文章有误,请在评论区下留言,我会尽快处理,非常感谢!
注:这个方法并不是最优的,只是笔者在当时想到的一种方法,关于最优方法,各位可以看看出题人自己给出的题解。比较简洁。
再次重申!本文描述的不是最优解!
原题体面
时间限制:1s
空间限制:256 MB
Let’s introduce some definitions that will be needed later.
Let
be the set of prime divisors of
. For example,
,
.
Let
be the maximum possible integer
where
is an integer such that
is divisible by
. For example:
(
is divisible by
but not divisible by
),
(
is divisible by
but not divisible by
).
Let
be the product of
for all
in
. For example:
,
.
You have integers
and
. Calculate
.
Input
The only line contains integers and ( ) — 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,
.
In the second example, actual value of formula is approximately
. Make sure you print the answer modulo
.
In the third example, be careful about overflow issue.
题面分析
的计算过程又臭又长,直接计算必定超时,所以我们考虑去转化问题。
首先,注意到
(
为满足
的最大整数)
=
我们枚举去枚举
,可以得到
=
然后,根据
的定义注意到
---------①
--------②
结合①②可以得到
----------③
稍加推导也可以得到
---------④
因此,我们得到一个初步的结论:
对于一个
,我们有
其中
是满足
的最大整数
容斥原理
有了上述结论后,我们只要计算出每种答案出现的次数即可。
但可以注意到,当
时,
出现的次数并不是
这么简单,
因为
是当
的所有情况总和。
因此我们考虑用容斥原理。
先计算
,它作为最大项,没有干扰,因此
出现的次数就是
。
对于
,它出现的次数并不是是
,而是要去掉之前
的次数
,
因此
出现的次数为
。
同理
出现的次数为
。
同理得到其他的答案出现次数。
记
为
的出现次数,则
有了
之积,答案自然就出来了。
其他的细节请查看代码。总体复杂度未知,初步估计是非常小的。
代码实现(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