Description
给定n,m,求
∑ni=1∑mj=1φ(i∗j) mod 1e9+7
n<=100,000,m<=1,000,000,000
Solution
以为这题是之前那道多校的原题,高兴地去刷了,然后,两天过去了。。
n
那么小显然暴力枚举即可
同样的,我们定义
然而这里并不是
free square number
不要紧,显然如果有个因数
p
有
那么一开始我们来一发质因数分解即可
然后同样的,
sum(n,m)=φ(p)∗sum(np,m)+sum(n,⌊mp⌋)
然后突然发现
m
TM那么大。。递归结尾返回什么。。
暴力搞肯定不行了,突然想到有种东西叫杜教筛
赶快拿起来研究了一下
就然我们来推导一下
又
带入原式可得
换一下枚举顺序,枚举i/d可得
网上有个小哥蛮逗,他杜教筛的时候暴力for后面的东西,然后吐槽说常数有点大。。
事实上大家都会按值域搞一发即可
复杂度懒得证明,直接拿来用吧如果预处理前面的项 O(n23)
接下来是精髓,卡常数的部分
照理说这样已经可以搞出来了,然而我在常数的坑里徘徊了n久
一开始我在sum的时候套了个map,然后MLE了5次(当然那时样例跑了20S+)
后来发现去掉map不仅时间变成了10S-而且不会MLE了。。
然后我在杜教筛的时候记搜中把map用手写hash_table,又快了2S
现在的时间变成了8S整
我从网上随手拔了个AC代码,亲测7.6S,交一发,A了。。
于是我就各种不爽。
我在筛素数的时候把每个数字的最大因数给筛出来,然后看似可以省掉一个
n√
?然而亲测没有效果。。
为什么那个代码同样在求sum的时候套了个map,为什么不好会MLE,而且可以跑到7.6S,遥(gui)遥(ce)领(ping)先(qi)
我再三反思,一定是辗转相除的次数太多了,调用递归的次数太多了。。
完了多半是废了。。
这时候我突然发现一开始的那个带进去算sum的东西因为已经因式分解了,所以值域很窄?
无脑套了个记忆化
跑出了3S惊人的速度!!!
欣慰地笑了,感觉自己可以卡世界上一切常数了
Code
/**************************************************************
Problem: 3512
User: bblss123
Language: C++
Result: Accepted
Time:8292 ms
Memory:91760 kb
****************************************************************/
#include<iostream>
#include<string.h>
#include<stdio.h>
#include<algorithm>
using namespace std;
const int M=7e6+5;
const int P=1e9+7;
typedef long long ll;
inline void Mod_add(int &a,int b){
if((a+=b)>=P)a-=P;
}
inline int Mod(int x){
if(x>=P)return x-P;
if(x<0)return x+P;
return x;
}
int prime[M>>2],phi[M],s[M],sf[M],t=0;
inline void Eular(){
phi[1]=1;
for(int i=2;i<M;++i){
if(!sf[i])prime[t++]=i,phi[i]=i-1,sf[i]=i;
for(int j=0,p;j<t&&1ll*(p=prime[j])*i<M;++j){
sf[i*p]=p;
if(i%p==0){phi[i*p]=p*phi[i];break;}
phi[i*p]=phi[i]*(p-1);
}
}
for(int i=1;i<M;++i)
s[i]=Mod(s[i-1]+phi[i]);
}
struct hash_table{
int tot,head[10000];
struct Edge{
int s,c,nxt;
}edge[100000];
inline hash_table(){memset(head,-1,sizeof(head));}
inline void add(int w,int &x,int &c){
edge[tot]=(Edge){x,c,head[w]};
head[w]=tot++;
}
inline int find(int &x){
int w=x>>20;
for(int i=head[w];~i;i=edge[i].nxt){
int s=edge[i].s;
if(s==x)return edge[i].c;
}
return -1;
}
inline void insert(int &x,int &val){
add(x>>20,x,val);
}
}mp;
inline int verphi(int x){
if(x<M)return s[x];
int w=mp.find(x);
if(~w)return w;
int k=0;
for(int i=2,s;i<=x;++i){
s=x/(x/i);
Mod_add(k,(ll)(s-i+1)*verphi(x/i)%P);
i=s;
}
k=(((ll)x*(x+1)>>1)-k)%P;
mp.insert(x,k);
return k;
}
inline int Divide(int x){
int res=1;
for(int i=0;prime[i]*prime[i]<=x;++i)
if(x%prime[i]==0){
int c=0;
for(;x%prime[i]==0;++c)x/=prime[i];
for(--c;c--;)res*=prime[i];
}
return res;
}
inline int sum(int n,int m){
if(!m)return 0;
if(m==1)return phi[n];
if(n==1)return verphi(m);
int &k=sf[n];
return ((ll)sum(n/k,m)*(k-1)+sum(n,m/k))%P;
}
int f[100005];
int main(){
Eular();
int n,m,ans=0;
scanf("%d %d",&n,&m);
memset(f,-1,sizeof(f));
for(int i=1;i<=n;++i){
ll k=Divide(i),t=i/k;
if(~f[t])Mod_add(ans,k*f[t]%P);
else Mod_add(ans,k*(f[t]=sum(t,m))%P);
}
printf("%d\n",ans);
return 0;
}

本文探讨了一道关于数论的问题,通过杜教筛算法高效解决大范围内的欧拉函数求和问题,并分享了优化过程及代码实现。
437

被折叠的 条评论
为什么被折叠?



