整除分块+逆元
详细题解移步P2260题解板块
式子可以拆开分别求解,具体见题解
这里主要讲的是整除分块(数论分块)和mod不为素数时如何求逆元
整除分块:求Σ「n/i」(i=1~n),「」表示向下取整
由于「n/i」在某段区间内都有相同的值,所以可以分块算,复杂度O( sqrt(n) )
code:
ll res=0; for(ll l=1,r;l<=n;l=r+1){
r=n/(n/l);
res=res+(r-l+1)*(n/l);
}
return res;
当mod是素数时,我们可以用费马小定理直接算,但是mod不为素数时,我们就可以用欧拉函数算(定义等右转Baidu)
当n,p互素时,n在 mod p 下的乘法逆元为 n^(phi(p)-1)
当n,p不互素时,并没有乘法逆元233333
特别地,当p为素数时,phi(p)=p-1,恰好是费马小定理
本人能力限制,不给出证明(逃
当所求逆元的数事先指定时,可以用暴力法(比如本题只需求2,6的逆元)
下面给出暴力法和欧拉函数的code:
#include<bits/stdc++.h> using namespace std; const int mod=19940417; template <typename T> int find_inv(T x){ //暴力预求 for(int i=1;i<=71806291;++i) //自行规定范围 if(1LL*x*i%mod==1) //根据定义 return i; } template <typename T> int _phi(T x){ //欧拉函数计算 int k=sqrt(x),phi=x; for(int i=2;i<=k;++i) if(x%i==0){ phi=phi/i*(i-1); while(x%i==0) x/=i; } if(x>1) phi=phi/x*(x-1); return phi; } int ksm(int x,int y){ int res=1; for(;y;y>>=1){ if(y&1) res=1LL*res*x%mod; x=1LL*x*x%mod; }return res; } int main(){ cout<<find_inv(2)<<" "<<find_inv(6)<<endl; int phi=_phi(mod); cout<<ksm(2,phi-1)<<" "<<ksm(6,phi-1); //2->9970209 6->3323403 return 0; }
接下来就是本题的code了
#include<iostream> #include<cstdio> using namespace std; typedef long long ll; template <typename T> inline T min(T &a,T &b) {return a<b ?a:b;} const int mod=19940417; ll n,m,ans; inline ll sum1(ll l,ll r) {return (l+r)*(r-l+1)%mod*9970209%mod;} //求等差数列 inline ll sum2(ll x) {return x*(x+1)%mod*(x*2+1)%mod*3323403%mod;} //求1^2+2^2+3^2+...+x^2 inline ll calc(ll x){ //整除分块(根据题意稍作修改) ll res=0; for(ll l=1,r;l<=x;l=r+1) r=x/(x/l),res=(res+(r-l+1)*x-sum1(l,r)*(x/l)+mod)%mod; return res; } int main(){ scanf("%lld%lld",&n,&m); if(n>m) swap(n,m); ans=calc(n)*calc(m)%mod; ll s1,s2,s3; for(ll l=1,r;l<=n;l=r+1){ r=min(n/(n/l),m/(m/l)); s1=n*m%mod*(r-l+1)%mod; s2=(n/l*m+m/l*n)%mod*sum1(l,r)%mod; s3=(n/l)*(m/l)%mod*(sum2(r)-sum2(l-1)+mod)%mod; ans=(ans-(s1-s2+s3+mod)%mod+mod)%mod; //式子拆分后分别求解 }printf("%lld",ans); return 0; }