前提条件のスキル:基本的な数論、メビウスの反転、ディリクレの畳み込み、DuJiaoふるい
トピック
回答
(作者はリッチテキストエディタを特に好みますので、以下の導出式はコピーできない写真に表示される場合があります)
質問の意味は非常に単純です。質問してください。モジュラスpは5e8以上の素数なので、逆元を見つけるので安心してください。
以下の式を押し始めます。
gcdがあり、すぐに要約する良い方法がないことがわかったので、gcdを起動しました。
最後の二つの部分は非常に精通している。我々はメビウス反転を使用することができることを見出した。う
、
(後者からそれを区別するためにメビウス反転によって導出関数を表すために使用するMサイズF)、
知覚分析が利用可能です、(定義
)、
だから、があります、
され、元に持ち込ま
式:
次に、ループ内にループがあることがわかりました。iを特定の数の因数形式に変更しない限り、現在の能力と直接合計することはでき
ません(私は不快すぎます)。代わりに、列挙順序を変更します。 dとiを列挙しますが、d * iとdを与えます。
だから、あり
ます;
尾の部分を見ると、私たちが少し慣れていることがわかります。これは、先ほど学習したディリクレの畳み込みの知識によると、
つまり
、
たくさんの置き換えられたオイラー関数をもたらすことができたので、それは素晴らしいことです:
、
設定してから
;
明らかに、値の種類はせいぜい
です。f関数の接頭辞の合計をすばやく取得できる場合は、数論を使用してブロックでそれを行うことができます。
したがって、DujiaoSieveを使用します。
設定し、
、
;
ルーチンによれば、G関数はF関数に応じて最適な完全統合関数として設定されるべきである。ここで、gはに設定され、その後、
、
によって、つまり
、を取得できます
;
したがって、接頭辞と関数hをすばやく見つける方法が得られます。これは、接頭辞と3次方程式です。
:そして、ドゥのティーチふるい設定し、(接頭辞と四角式:
)
このとき、数論ブロックを使用すると、f関数の接頭辞の合計をより速く取得できます。
最後に、2つのプロセスが組み合わされている。による杜の時間複雑分析礁渓、複雑さは全てが平坦化されても達成されることはありませんはい、
。
コード
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<vector>
#include<queue>
#include<map>
#include<tr1/unordered_map>
#define ll long long
#define MAXN 10000005
using namespace std;
using namespace tr1;
inline ll read(){
ll x=0;bool f=1;char s=getchar();
while((s<'0'||s>'9')&&s>0){if(s=='-')f^=1;s=getchar();}
while(s>='0'&&s<='9')x=(x<<1)+(x<<3)+s-'0',s=getchar();
return f?x:-x;
}
ll n,p[MAXN],sp[MAXN],MOD=read();
unordered_map<ll,ll>ph;
bool nop[MAXN];
vector<ll>h;
inline ll ksm(ll a,ll b){
ll res=1;
for(;b;b>>=1){
if(b&1)res=res*a%MOD;
a=a*a%MOD;
}
return res;
}
ll NI=ksm(6,MOD-2);//提前求逆元
inline void build(){
nop[0]=nop[1]=1;
for(int i=1;i<MAXN-4;i++)p[i]=i;
for(int a=2;a<MAXN-4;a++){
if(!nop[a])h.push_back(a),p[a]=a-1;
for(int i=0,u;i<h.size()&&h[i]*a<MAXN-4;i++){
u=a*h[i],nop[u]=1;
if(a%h[i]==0)p[u]=p[a]*h[i],i=MAXN;
else p[u]=p[a]*p[h[i]];
}
}
sp[1]=p[1];
for(int i=2;i<MAXN-4;i++)sp[i]=(sp[i-1]+p[i]*i%MOD*i%MOD)%MOD;
}
inline ll sumx(ll n){n%=MOD;//n的范围比模数大就离谱
return (n*(n+1)>>1)%MOD; //一次前缀和(sum)
}
inline ll sumy(ll n){n%=MOD;
return n*(n+1)%MOD*(n*2+1)%MOD*NI%MOD; //平方前缀和
}
inline ll sumphi(ll n){
if(n<MAXN-4)return sp[n];
if(ph.find(n)!=ph.end())return ph[n];
ll res=sumx(n)*sumx(n)%MOD;
for(ll i=2,ls;i<=n;i=ls+1)
ls=n/(n/i),res=(res-(sumy(ls)-sumy(i-1)+MOD)%MOD*sumphi(n/i)%MOD+MOD)%MOD;
return ph[n]=res;
}
inline ll getans(ll n){
ll res=0;
for(ll i=1,ls;i<=n;i=ls+1)
ls=n/(n/i),res=(res+(sumphi(ls)-sumphi(i-1)+MOD)%MOD*sumx(n/i)%MOD*sumx(n/i)%MOD)%MOD;
return res;
}
int main()
{
build();n=read();
printf("%lld\n",getans(n));
return 0;
}