【2020杭电多校round1 1010】HDU6760 Math is Simple
题目大意
\(T\)次询问。每次给定\(n\)。求
输出答案在\(\bmod 998244353\)意义下的值。
数据范围:\(1\leq T\leq 10^4\),\(1\leq n\leq 10^8\)。
本题题解
记\(n\)的答案为\(f_n\)。即:
考虑递推\(f_n\)。那么:
发现后面两坨东西长得很像,只是\(n\)变成了\(n-1\)。于是我们设:
则上面的递推式可以写为:
又因为\(f_2=\frac{1}{2}\),\(g_{2}=0\)(这可以手算出来),所以\(f_n=\frac{1}{2}+g_n\)。
于是问题就转化为了求\(g_n\)。因为\(a+b=n\)。我们考虑只枚举\(a\),则\(b=n-a\)。发现\(\gcd(a,n-a)=1\)的充分必要条件是\(\gcd(a,n)=1\)。再观察\(\frac{1}{ab}\),可以写成\(\frac{1}{a(n-a)}=\frac{1}{n}\cdot \frac{n}{a(n-a)}=\frac{1}{n}\cdot(\frac{1}{a}+\frac{1}{n-a})\)。因此:
这看起来比较简洁。因此很有希望推出正确的做法,我们继续推:
发现\(\sum_{i=1}^{m}\frac{1}{i}\)这个东西是可以预处理出来的。记为\(S(m)\)。则:
因为\(n\)的约数数量是\(O(\sqrt{n})\)的。所以我们可以用\(O(n+T\sqrt{n})\)的时间复杂度解决本题。
但是因为\(n\)太大,空间也很卡。具体来说,空间限制是\(512\text{MB}\),一个大小为\(10^8\)的\(\texttt{int}\)型数组,大小为\(382\text{MB}\),所以我们只能开一个这样的数组。前面说过要预处理出\(S\)。那么\(\mu\)就不能用线性筛预处理出来。我们可以先分解出\(n\)的所有质因子,然后用\(\text{dfs}\)来枚举每个质因子的次幂,以此枚举出约数\(d\),顺便求出\(\mu\)。并且,\(S\)数组也只能开一个,那么有一种先求阶乘,再推逆元的做法就不管用了,必须利用下面这个公式求逆元:
边界是\(1^{-1}=1\)。
参考代码:
//problem:HDU6760
#include <bits/stdc++.h>
using namespace std;
#define pb push_back
#define mk make_pair
#define lob lower_bound
#define upb upper_bound
#define fi first
#define se second
#define SZ(x) ((int)(x).size())
typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;
template<typename T>inline void ckmax(T& x,T y){x=(y>x?y:x);}
template<typename T>inline void ckmin(T& x,T y){x=(y<x?y:x);}
const int MOD=998244353;
inline int mod1(int x){return x<MOD?x:x-MOD;}
inline int mod2(int x){return x<0?x+MOD:x;}
inline void add(int& x,int y){x=mod1(x+y);}
inline void sub(int& x,int y){x=mod2(x-y);}
inline int pow_mod(int x,int i){int y=1;while(i){if(i&1)y=(ll)y*x%MOD;x=(ll)x*x%MOD;i>>=1;}return y;}
const int MAXN = 1e8;
const int MAXC = 30;
int n,inv_2,inv_n;
int S[MAXN+5];
void init(){
inv_2 = pow_mod(2,MOD-2);
S[1]=1;
for(int i=2;i<=MAXN;++i)
S[i]=mod2( - (ll)(MOD/i) * S[MOD%i] % MOD);
for(int i=2;i<=MAXN;++i)
add(S[i],S[i-1]);
}
int p[MAXC+5],cnt,ans;
void dfs(int idx,int d,int mu){
if(idx == cnt+1){
mu=mod2(mu);
add(ans,(ll)mu * mod2(S[d]-S[d-1]) %MOD * S[n/d] %MOD);
return;
}
dfs(idx+1,d,mu);
dfs(idx+1,d*p[idx],-mu);
// 次数>=2时,mu=0,对答案没有贡献,不用递归
}
void solve_case(){
cin>>n;
if(n==2){
cout<<inv_2<<endl;
return;
}
inv_n = pow_mod(n,MOD-2);
int tmp=n;
cnt=0;
for(int i=2;i*i<=n;++i)
if(n%i==0){
p[++cnt]=i;
while(n%i==0)
n/=i;
}
if(n!=1)
p[++cnt]=n;
// for(int i=1;i<=cnt;++i)
// cerr<<p[i]<<" ";
// cerr<<endl;
n=tmp;
ans=0;
dfs(1,1,1);
ans=(ll)ans*inv_n%MOD;
add(ans,inv_2);
cout<< ans <<endl;
}
int main() {
init();
int T;cin>>T;while(T--){
solve_case();
}
return 0;
}
补充一下,最后推\(g_n\)那段,我们主要用了\(\sum_{d|n}\mu(d)=[n=1]\)这个结论。这只能说是用到了莫比乌斯函数,而不是真正的莫比乌斯反演。
不过用反演也能推。设\(F(x)=\sum_{i=1}^{n}\frac{1}{i}[\gcd(i,n)=x]\),则\(g_n=\frac{1}{n}F(1)\),我们就是要求\(F(1)\)。
设\(G(x)=\sum_{x|d}F(d)\),可以推出\(G(x)=[x|n]\sum_{i=1}^{\frac{n}{x}}\frac{1}{ix}\)。
再套用莫比乌斯反演的结论,\(F(x)=\sum_{x|d}G(d)\mu(\frac{d}{x})\),可得\(F(1)=\sum_{d|n}\mu(d)\frac{1}{d}\sum_{i=1}^{\frac{n}{d}}\frac{1}{i}\)。这与我们推出的式子是一样的,相当于一个验证。