題目鏈接:傳送門
前置技能:
解題思路:
點亮前置技能就珂以發現這是裸題……
首先杜教篩要卷積一個函數。
先考慮的情況:
發現有一條式子叫做。
所以讓兩邊卷積一個,得到
按照杜教篩的套路亂搞一波(此處可直接套杜教篩的結論),得到:
然後整除分塊+記憶化亂搞即可。
對於的情況,也類似地套模板即可。
奇技淫巧
發現遞歸的時候很大,記憶化不太好搞,這裏需要map奇技淫巧來解決。
遞歸時需要記憶化的值只有(這個先不證明)
因爲當遞歸傳遞的值小於(一般設爲左右)時,是直接線性篩出來的,所以珂以考慮對於,把需要記憶化的存在的位置。
因爲(否則調用線性篩的結果直接返回),在左右,所以在以下。
這樣就珂以不用開一個的數組或是來記憶化了。
這裏放一張圖:
但是這樣會不會衝突呢?比如會不會與不相等,導致需要記憶化的位置不只有呢?
這裏需要引進一條玄學定理,叫做。
這裏就不證明了,珂以看OIWiki-莫比烏斯反演中的證明qwq
根據引理珂以看出不存在之外需要記憶化的位置。
所以如上亂搞即可qwq
另外,這題卡常數,要把兩個前綴和在一個函數裏面求出來。
毒瘤代碼
#include<stdio.h>
#include<cstring>
#include<algorithm>
#include<math.h>
#define re register int
#define mod 1000000000
using namespace std;
typedef long long ll;
int read() {
re x=0,f=1;
char ch=getchar();
while(ch<'0' || ch>'9') {
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0' && ch<='9') {
x=10*x+ch-'0';
ch=getchar();
}
return x*f;
}
const int Size=2000005;
ll tot,mu[Size],phi[Size],prime[Size];
ll summu[Size],sumphi[Size];
bool vis[Size];
void getp(int maxn) {
phi[1]=mu[1]=1;
phi[0]=0;
for(re i=2; i<=maxn; i++) {
if(!vis[i]) {
prime[++tot]=i;
mu[i]=-1;
phi[i]=i-1;
}
for(re j=1; j<=tot; j++) {
int now=i*prime[j];
if(now>maxn) break;
vis[now]=true;
if(i%prime[j]==0) {
mu[now]=0;
phi[now]=phi[i]*prime[j];
break;
}
phi[now]=phi[i]*phi[prime[j]];
mu[now]=-mu[i];
}
}
for(re i=1; i<=maxn; i++) {
summu[i]=summu[i-1]+mu[i];
sumphi[i]=sumphi[i-1]+phi[i];
}
}
ll n,m1[Size],m2[Size];
bool vis1[Size],vis2[Size];
pair<ll,ll> getans(ll x) {
if(x<=2000000) return make_pair(sumphi[x],summu[x]);
int t=n/x;
if(vis1[t]) return make_pair(m1[t],m2[t]);
ll sum1=0,sum2=0,lst;
for(ll i=2; i<=x; i=lst+1) {
lst=x/(x/i);
pair<ll,ll> tmp=getans(x/i);
sum1+=(lst-i+1)*tmp.first;
sum2+=(lst-i+1)*tmp.second;
}
vis1[t]=true;
m1[t]=(x*(x+1ll)>>1ll)-sum1;
m2[t]=1ll-sum2;
return make_pair(m1[t],m2[t]);
}
int main() {
getp(2000000);
int T=read();
while(T--) {
n=read();
memset(vis1,0,sizeof(vis1));
memset(vis2,0,sizeof(vis2));
pair<ll,ll> ans=getans(n);
printf("%lld %lld\n",ans.first,ans.second);
}
return 0;
}
/*
1
2147483647
*/