数论习题(一)
(一).推式子题
- P2568 GCD
给定 \(n\),求:
其中 \(\mathbb{P}\) 为质数集。
推式子:
\(\text{①}\):感性理解,\(\lfloor\frac{n}{p}\rfloor\) 以内的互质数对对数,等于二倍的 \(i\ge j\) 的对数,减掉重复计算的 \((1,1)\) 数对。
线性筛 \(\varphi\) 并求前缀和,枚举 \(p\) 统计即可。
点击查看代码
//#pragma GCC optimize(2)
//#pragma GCC optimize(3)
//#pragma GCC optimize("Ofast","inline")
#include<bits/stdc++.h>
#define fr(a) freopen(a,"r",stdin)
#define fw(a) freopen(a,"w",stdout)
using namespace std;
typedef long long ll;
const int MAXN=1e7+10;
ll n,prime[MAXN],phi[MAXN],v[MAXN],tot,sumphi[MAXN];
ll ans;
void shai(){//欧拉筛求质数&欧拉函数
phi[1]=1;
for(int i=2;i<=n;i++){
if(v[i]==0){
v[i]=i;prime[++tot]=i;
phi[i]=i-1;
}
for(int j=1;j<=tot;j++){
if(prime[j]>v[i] || prime[j] > n/i) break;
v[i*prime[j]]=prime[j];
if(i%prime[j]==0) phi[i*prime[j]] =phi[i] * prime[j];
else phi[i*prime[j]] =phi[i] *(prime[j]-1);
}
}
for(int i=1;i<=n;i++) phi[i]+=phi[i-1];
}
int main(){
scanf("%lld",&n);
shai();
for(int i=1;i<=tot;i++) ans+=2*phi[n/prime[i]]-1;
printf("%lld\n",ans);
return 0;
}
- P2398 GCD SUM
给定 \(n\),求:
枚举 \(\gcd\),枚举推式子:
筛 \(\varphi\),前缀和,枚举 \(d\),搞定!
点击查看代码
//#pragma GCC optimize(2)
//#pragma GCC optimize(3)
//#pragma GCC optimize("Ofast","inline")
#include<bits/stdc++.h>
#define fr(a) freopen(a,"r",stdin)
#define fw(a) freopen(a,"w",stdout)
#define MP(a,b) make_pair(a,b)
#define DEBUG
using namespace std;
typedef long long ll;
const int N=1e5+10;
ll n;
ll prime[N],v[N],phi[N],tot;
void shai(){//欧拉筛求质数&欧拉函数
phi[1]=1;
for(int i=2;i<=n;i++){
if(v[i]==0){
v[i]=i;prime[++tot]=i;
phi[i]=i-1;
}
for(int j=1;j<=tot;j++){
if(prime[j]>v[i] || prime[j] > n/i) break;
v[i*prime[j]]=prime[j];
if(i%prime[j]==0) phi[i*prime[j]] =phi[i] * prime[j];
else phi[i*prime[j]] =phi[i] *(prime[j]-1);
}
}
for(int i=1;i<=n;i++) phi[i]+=phi[i-1];
}
int main(){
scanf("%lld",&n);ll ans=0;
shai();
for(int d=1;d<=n;d++){
ans+=d*(2*phi[n/d] -1);
}printf("%lld\n",ans);
return 0;
}
- P2522 [HAOI2011] Problem b
给定 \(1\le (a\le b,c\le d,k) \le 5\cdot 10^4\),求:
先通过容斥拆成四部分:记 \(A=\lceil\frac{a}{k}\rceil,B=\lfloor\frac{b}{k}\rfloor,C=\lceil\frac{c}{k}\rceil,D=\lfloor\frac{d}{k}\rfloor\)
记 \(f(n,m)=\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{m}[\gcd(i,j)=1]\),则
\(\text{原式}=f(B,D)-f(B,C-1)-f(A-1,D)+f(A-1,C-1)\)
开始推 \(f\)。
\(\begin{aligned}f(n,m)&=\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{m}[\gcd(i,j)=1] \\ &=\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{m}\sum\limits_{d\mid \gcd(i,j)}\mu(d) &\text{好像是莫反的结论?} \\&=\sum\limits_{d=1}^{\min(n,m)} \Big(\mu(d) \cdot \sum\limits_{i=1}^{n}[d\mid i] \cdot \sum\limits_{j=1}^{m} [d\mid j]\Big) &\text{更换求和顺序,感性理解} \\&=\sum\limits_{d=1}^{\min(n,m)} \Big(\mu(d) \lfloor\frac{n}{d}\rfloor \lfloor\frac{m}{d}\rfloor\Big) &n\text{ 以内 }d\text{ 的倍数一共有 } \lfloor\frac{n}{d}\rfloor \text{ 个} \end{aligned}\)
最后这个式子可以数论分块求,这样这道题就做完了。
点击查看代码
//#pragma GCC optimize(2)
//#pragma GCC optimize(3)
//#pragma GCC optimize("Ofast","inline")
#include<bits/stdc++.h>
#define fr(a) freopen(a,"r",stdin)
#define fw(a) freopen(a,"w",stdout)
#define MP(a,b) make_pair(a,b)
#define DEBUG
using namespace std;
typedef long long ll;
const int N=5e4+10;
ll T=1;
ll a,b,c,d,k;
ll prime[N],v[N],mu[N],tot,summu[N];
void shai(){
mu[1]=1;
for(int i=2;i<=N-10;i++){
if(v[i]==0){
v[i]=i;prime[++tot]=i;
mu[i]=-1;
}
for(int j=1;j<=tot;j++){
if(prime[j]>v[i] || prime[j] > (N-10)/i) break;
v[i*prime[j]]=prime[j];
if(i % prime[j]==0) mu[i*prime[j]]=0;
else mu[i*prime[j]]=-mu[i];
}
}
for(int i=1;i<=N-10;i++) summu[i]=summu[i-1]+mu[i];
}
ll calc(ll n,ll m){
ll res=0,l=1,lim=min(n,m);bool Out=0;
while(l<=lim){
ll pn=n/l,pm=m/l;
ll r=min(n/pn,m/pm);
if(r>=lim) r=lim,Out=1;
res+=(summu[r]-summu[l-1]) * pn * pm;
if(Out) break;
l=r+1;
}
return res;
}
ll calc2(ll n,ll m){
ll res=0;
for(int d=1;d<=min(n,m);d++){
res+=mu[d] * (n/d) * (m/d);
}return res;
}
int main(){
shai();
scanf("%lld",&T);
for(int Case=1;Case<=T;Case++){
scanf("%lld%lld%lld%lld%lld",&a,&b,&c,&d,&k);
ll ans=0;
ll A=ceil((double)a/k)-1,B=b/k,C=ceil((double)c/k)-1,D=d/k;
ans=calc(B,D)-calc(B,C)-calc(A,D)+calc(A,C);
printf("%lld\n",ans);
}
return 0;
}
- P1829 [国家集训队] Crash的数字表格 / JZPTAB
给定 \(n,m\),求:
由小学奥数可知:\(\operatorname{lcm}(i,j)=\frac{ij}{\gcd(i,j)}\),所以:
接下来我们化简括号内,\(D\) 之后的一部分(记为 \(f(n,m)\)):
此式中 \(\mu(d)\) 之后的一部分,相当于这个东西:
所以:
而中间那个求和符号:
所以:
\(f\) 就可以用数论分块来算。
而原式显然也可以数论分块来做,两个合二为一就能过掉此题了。
最后放一下原式最终的样子:
点击查看代码
//#pragma GCC optimize(2)
//#pragma GCC optimize(3)
//#pragma GCC optimize("Ofast","inline")
#include<bits/stdc++.h>
#define fr(a) freopen(a,"r",stdin)
#define fw(a) freopen(a,"w",stdout)
#define MP(a,b) make_pair(a,b)
#define DEBUG
using namespace std;
typedef long long ll;
const int N=1e7+10,mod=20101009,inv2=10050505,inv4=15075757;
ll n,m;
ll prime[N],v[N],mu[N],tot;
ll sum2[N];
void shai(ll n){
mu[1]=1;
for(ll i=2;i<=n;i++){
if(v[i]==0){
v[i]=i;prime[++tot]=i;
mu[i]=-1;
}
for(ll j=1;j<=tot;j++){
if(prime[j]>v[i] || prime[j] > n/i) break;
v[i*prime[j]]=prime[j];
if(i % prime[j]==0) mu[i*prime[j]]=0;
else mu[i*prime[j]]=-mu[i];
}
}
}
ll calc(ll n,ll m){
ll res=0;
ll l=1,lim=min(n,m);bool Out=0;
while(l<=lim){
ll nl=n/l,ml=m/l,r=min(n/nl,m/ml);
if(r>=lim) r=lim,Out=1;
ll cnt=nl*(nl+1)%mod*ml%mod*(ml+1)%mod *inv4%mod;
res=(res+(sum2[r]-sum2[l-1])*cnt%mod)%mod;
if(Out) break;
l=r+1;
}
return (res%mod+mod)%mod;
}
int main(){
scanf("%lld%lld",&n,&m);
shai(max(n,m));
for(ll i=1;i<=max(n,m);i++) sum2[i]=(sum2[i-1]+i*i%mod*mu[i]%mod)%mod;
ll ans=0,l=1,lim=min(n,m);
bool Out=0;
while(l<=lim){
ll r=min(n/(n/l),m/(m/l));
if(r>=lim) r=lim,Out=1;
ans=(ans+calc(n/l,m/l)*(r*(r+1)%mod-l*(l-1)%mod)%mod*inv2%mod)%mod;
if(Out) break;
l=r+1;
}
printf("%lld\n",(ans%mod+mod)%mod);
return 0;
}
- BZOJ2694. Lcm
给定 \(1\le t\le 10^4\) 组 \(1\le n,m\le 4\cdot 10^6\),求 \(i\in[1,n]\,,\,j\in[1,m]\) 且 \(\forall n,n^2 \nmid\gcd(i,j)\) 的数对 \((i,j)\) 个数,即:
直接推式子,把 \(\operatorname{lcm}\) 变成 \(\gcd\),然后枚举 \(\gcd\) :
对 \(\operatorname{lcm}\) 的处理和上一题很像:
这样子上一题中的 \(f\) 就出来了,省略中间一大堆推导过程:
发现 \(kd\le\min(n,m)\),令 \(T=kd\),经典套路,枚举 \(T\):\(\color{red}\tiny\text{注:在做这道题前我全然不知道这种套路}\)
令 \(f(k)=k\cdot \mu(k)\,,\,g(k)=\mu^2(k)\,,\,h(T)=\sum\limits_{k\mid T}k\cdot\mu(k)\cdot\mu^2(\frac{T}{k})\)
则 \(f,g\) 为积性函数,又 \(h=f*g\),得 \(h\) 为积性函数。
考虑用线性筛筛出来 \(h\),易得:
这样就可以用线性筛筛出\(h\) 了。
最后对原式进行数论分块,卡卡常就过去了。
点击查看代码
#pragma GCC optimize(2)
#pragma GCC optimize(3)
#pragma GCC optimize("Ofast","inline")
#include<bits/stdc++.h>
#define fr(a) freopen(a,"r",stdin)
#define fw(a) freopen(a,"w",stdout)
#define MP(a,b) make_pair(a,b)
#define DEBUG
using namespace std;
typedef int ll;
const int N=4e6+10,mod=1<<30;
ll T=1;
ll n,m,sum[N];
ll prime[N],v[N],g[N],h[N],tot;
void shai(ll n){
h[1]=1;
for(int i=2;i<=n;i++){
if(v[i]==0){
prime[++tot]=i;
v[i]=i;g[i]=i;
}
for(int j=1;j<=tot;j++){
if(prime[j]>v[i]||prime[j]>n/i) break;
v[i*prime[j]]=prime[j];
if(i%prime[j]==0) g[i*prime[j]]=g[i]*prime[j];
else g[i*prime[j]]=prime[j];
}
}
for(int i=2;i<=n;i++){
if(i==g[i]){
if(g[i]==v[i]) h[i]=1-v[i];
else if(g[i]==v[i]*v[i]) h[i]=-v[i];
else h[i]=0;
}else h[i]=(h[i/g[i]]*h[g[i]])%mod;
}
for(int i=1;i<=n;i++) h[i]=(h[i]*i%mod+h[i-1])%mod,sum[i]=(sum[i-1]+i)%mod;
}
int main(){
shai(N-10);
scanf("%d",&T);
for(int Case=1;Case<=T;Case++){
scanf("%d%d",&n,&m);
ll ans=0;
for(int l=1,r;l<=min(n,m);l=r+1){
ll nl=n/l,ml=m/l;
r=min(n/nl,m/ml);
ans=(ans+sum[nl]*sum[ml] %mod *(h[r]-h[l-1])%mod)%mod;
}
printf("%d\n",(ans+mod)%mod);
}
return 0;
}
- P4449 于神之怒加强版
给定 \(1\le T\le 2\cdot 10^3\) 组 \(1\le n,m\le 5\cdot 10^6\),和一个固定的 \(1\le \alpha\le 5\cdot 10^6\),求:
套路!!枚举 \(\gcd\):
莫反拆开:
枚举 \(k\):
也就是:
枚举 \(T=dk\):
后面那个东西其实是 \(id^\alpha*\mu\),显然是积性函数,可以线性筛筛出来(\(p\in\mathbb{P},k\ge 1\)):
筛出来后求个前缀和,然后数论分块即可。
点击查看代码
//#pragma GCC optimize(2)
//#pragma GCC optimize(3)
//#pragma GCC optimize("Ofast","inline")
#include<bits/stdc++.h>
#define fr(a) freopen(a,"r",stdin)
#define fw(a) freopen(a,"w",stdout)
#define MP(a,b) make_pair(a,b)
#define DEBUG
using namespace std;
typedef long long ll;
const int N=5e6+10,mod=1e9+7;
ll T=1,k;
ll n,m;
ll prime[N],tot,v[N],mu[N];
ll g[N],f[N];
ll qpow(ll a,ll n){
ll res=1;
while(n){
if(n&1) res=(res*a) %mod;
a=(a*a)%mod,n>>=1;
}return res;
}
void shai(ll n){
f[1]=mu[1]=g[1]=f[1]=1;
for(int i=2;i<=n;i++){
if(!v[i]){
v[i]=i,prime[++tot]=i,mu[i]=-1;
g[i]=i;
}
for(int j=1;j<=tot;j++){
if(prime[j]>v[i]||prime[j]>n/i) break;
v[i*prime[j]]=prime[j];
if(i%prime[j]==0) g[i*prime[j]]=g[i]*prime[j],mu[i*prime[j]]=0;
else g[i*prime[j]]=prime[j],mu[i*prime[j]]=-mu[i];
}
}
for(int i=2;i<=n;i++){
if(i==g[i]){
ll tot=0;
while(g[i]&&g[i]%v[i]==0) tot++,g[i]/=v[i];
f[i]=qpow(v[i],k*tot)-qpow(v[i],k*(tot-1));
f[i]=(f[i]%mod+mod)%mod;
}else f[i]=(f[i/g[i]]*f[g[i]])%mod;
}
for(int i=1;i<=n;i++) f[i]+=f[i-1];
}
int main(){
scanf("%lld%lld",&T,&k);
shai(N-10);
for(int Case=1;Case<=T;Case++){
scanf("%lld%lld",&n,&m);
ll ans=0;
for(int l=1,r;l<=min(n,m);l=r+1){
ll nl=n/l,ml=m/l;r=min(n/nl,m/ml);
(ans+=nl*ml%mod*(f[r]-f[l-1])%mod)%=mod;
}printf("%lld\n",(ans+mod)%mod);
}
return 0;
}
- P3327 [SDOI2015] 约数个数和
给定 \(1\le t\le 5\cdot 10^4\) 组 \(1\le n,m\le 5\cdot 10^4\),求:
其中 \(d(n)\) 为 \(n\) 的约数个数,即 \(\sum\limits_{d\mid n}1\)
蒙结论,蒙出来要求的就是这个东西:
原理大概就是:枚举一对互质(为什么不会遗漏我也不知道)的数对 \((i,j)\),满足 \(ab\) 存在因数 \(ij\),且 \(a\le n\,,\,b\le m\) 的数对 \((a,b)\) 的数量显然为 \(\lfloor\frac{n}{i}\rfloor\lfloor\frac{m}{j}\rfloor\)。
但是为什么枚举互质的 \((i,j)\) 就能做到不重不漏,我也不得而知。
接下来这个东西就能做了,用莫反变一下 \([\gcd(i,j)=1]\):
套路性地,把 \(d\) 提出去:
也就是:
我们 \(O(n\sqrt {n})\) 预处理一下 \(\sum\limits_{i=1}^{n} \Big\lfloor\dfrac{n}{i}\Big\rfloor\) ,原式最外层整除分块,这个就做完了。
附:关于此题正常一些的想法
正常情况下应该蒙出这个结论的:
我们先把这个证了。
记:
我们知道 \(d(nm)=\prod\limits_{i=1}^{k}(a_i+b_i+1)\),我们分别考虑每个质因子。
对于某个质因子 \(p_i\),我们分别考虑等式左右的取法。
对于等式左边,显然取法有 \(a_i+b_i+1\) 种。
对于等式右边,有三类取法:
加起来一共有 \(a_i+b_i+1\) 种取法。
而每个质因子都是一样的情形,所以总数相同。
有了这个结论,我们正常地推一遍式子:
直接考虑更换 \(x,y\) 和 \(i,j\) 的枚举顺序:
而 \(\sum\limits_{i=1}^{n}[x\mid i]=\Big\lfloor\dfrac{n}{x}\Big\rfloor\),所以原式:
此时已经和我蒙出来的结论一模一样了,这就OK了。
点击查看代码
//#pragma GCC optimize(2)
//#pragma GCC optimize(3)
//#pragma GCC optimize("Ofast","inline")
#include<bits/stdc++.h>
#define fr(a) freopen(a,"r",stdin)
#define fw(a) freopen(a,"w",stdout)
#define MP(a,b) make_pair(a,b)
#define DEBUG
using namespace std;
typedef long long ll;
const int N=5e4+10;
ll TT=1;
ll n,m;
ll prime[N],v[N],mu[N],tot;
ll ji[N];
ll calc(ll n){
if(ji[n]) return ji[n];
ll res=0;
for(int l=1,r;l<=n;l=r+1){
ll nl=n/l;
r=n/nl;
res+=nl * (r-l+1);
}return ji[n]=res;
}
void shai(ll n){
mu[1]=1;
for(ll i=2;i<=n;i++){
if(v[i]==0){
v[i]=i;prime[++tot]=i;
mu[i]=-1;
}
for(ll j=1;j<=tot;j++){
if(prime[j]>v[i] || prime[j] > n/i) break;
v[i*prime[j]]=prime[j];
if(i % prime[j]==0) mu[i*prime[j]]=0;
else mu[i*prime[j]]=-mu[i];
}
}
for(int i=1;i<=n;i++) mu[i]+=mu[i-1],ji[i]=calc(i);
}
int main(){
shai(N-10);
scanf("%lld",&TT);
for(int Case=1;Case<=TT;Case++){
scanf("%lld%lld",&n,&m);
ll ans=0;
for(int l=1,r;l<=min(n,m);l=r+1){
ll nl=n/l,ml=m/l;
r=min(n/nl,m/ml);
ans+=(mu[r]-mu[l-1]) * ji[nl]*ji[ml];
}
printf("%lld\n",ans);
}
return 0;
}
- BZOJ3561. DZY Loves Math VI
给定 \(1 \le n,m\le 5\cdot 10^5\),求:
直接推式子,枚举 \(\gcd\):
\(d^d\) 提出去,莫反拆开:
套路地,枚举 \(k\):
\(k^{2d}\) 提出去,关于 \(i,j\) 的求和分开:
本来我觉得的这个东西还能再变形,再做,于是想破脑袋想了一个上午没想出来。
但其实,我们分析分析复杂度:
\(d\) 的循环 \(O(n)\)
\(d\) 和 \(k\) 的循环是 \(\dfrac{n}{1}+\dfrac{n}{2}+\dots \dfrac{n}{n}=O(n\log n)\)
如果再算上 \(i\) 和 \(j\) 的循环,那就是 \(\dfrac{n}{1}\log(\dfrac{n}{1})+\dfrac{n}{2}\log(\dfrac{n}{2})+\dots \dfrac{n}{n}\log(\dfrac{n}{n})\),这个东西不超过 \(O(n\log^2 n)\),具体是多少不会算,但其实很小。
因为当 \(d\) 取了 \(d_0\) 时,最内层用到的最大的幂就是 \(\lfloor\frac{n}{d_0}\rfloor ^ d\),于是就可以实时计算 \(1\sim \lfloor\frac{n}{d_0}\rfloor\) 的 \(d\) 次幂,如果在 \(d_0-1\) 时算过了,只需要自乘一下,否则直接用快速幂算。因为每个自然数最多算一次快速幂,这一些预处理的总复杂度就是 \(O(n\log n)\) 的。
预处理之后,可以同时把幂的前缀和算出来,从时间复杂度就到了 \(O(n\log n)\)。
所以直接枚举算这个式子就好了……我为什么浪费了一个上午啊啊啊啊啊啊啊啊啊啊啊啊
点击查看代码
#pragma GCC optimize(2)
#pragma GCC optimize(3)
#pragma GCC optimize("Ofast","inline")
#include<bits/stdc++.h>
#define fr(a) freopen(a,"r",stdin)
#define fw(a) freopen(a,"w",stdout)
#define MP(a,b) make_pair(a,b)
#define DEBUG
using namespace std;
typedef long long ll;
const int N=5e5+10,mod=1000000007;
int n,m;
int prime[N],v[N],mu[N],tot;
ll now[N],sum[N];
int tmp[N];
ll qpow(ll a,int n){
int res=1,stn=n,sta=a;
while(n){
if(n&1) res=(1ll*res*a)%mod;
a=(a*a)%mod,n>>=1;
}
return res;
}
void shai(int n){
mu[1]=1,v[1]=1;
for(int i=2;i<=n;i++){
if(v[i]==0){
v[i]=i;prime[++tot]=i;
mu[i]=-1;
}
for(int j=1;j<=tot;j++){
if(prime[j]>v[i] || prime[j] > n/i) break;
v[i*prime[j]]=prime[j];
if(i % prime[j]==0) mu[i*prime[j]]=0;
else mu[i*prime[j]]=-mu[i];
}
}
}
int main(){
shai(N-10);
scanf("%d%d",&n,&m);
int ans=0;
for(int d=1;d<=min(n,m);d++){
int tot=0,lim=min(n/d,m/d);
for(int i=1;i<=max(n/d,m/d);i++){
now[i]=((now[i]!=0)?now[i] *i%mod:qpow(i,d))%mod;
sum[i]=(sum[i-1]+now[i])%mod;
}
for(int k=1;k<=lim;k++){
int cnt1=0,cnt2=0;tmp[1]=1;
int lim1=n/(d*k),lim2=m/(d*k);
tot=(tot+ mu[k] * qpow(k,2*d)%mod *sum[lim1]%mod *sum[lim2]%mod )%mod;
}
ans=(ans+tot*qpow(d,d)%mod)%mod;
}printf("%d\n",(ans+mod)%mod);
return 0;
}
- P5518 [MtOI2019] 幽灵乐团 / 莫比乌斯反演基础练习题
推式子大成!
求:
其中:
预处理
我们将分子分母分离:
也就是:
对分子进行子母分离,分母扔下面:
关键东西
我们定义一个神奇函数 \(g\),满足:
这个神奇函数可以 \(O(n)\) 求。
有了神奇函数,我们就可以推导一下这个式子(待会要用到两次,所以扔前头了):
枚举 \(\gcd\),有:
莫反拆开往外提:
把上面的求和捋下来,有:
枚举 \(T=dk\),里面我们不枚举 \(d\) 枚举 \(k\),有:
我们把底数上的分子 \(T\) 和分母 \(k\) 拆开处理:
分子
因为底数为 \(T\) 不变,我们把求积符号写在指数上,有:
这东西熟悉的不得了,不就是 \(1*\mu=\varepsilon\) 嘛!于是就成了:
这玩意一眼顶针,铁定为 \(1\)。
分母
我们记 \(f(T)=\prod\limits_{k\mid T}k ^{\mu(k)}\)。
没办法拆开了,于是我们考虑一下这玩意的实际含义。
我们对 \(T\) 进行质因数分解,记为 \(T=\prod\limits_{i=1}^{m}p_i^{c_i}\)
我们也对 \(k\) 进行分解:\(k=\prod\limits_{i=1}^{m}p_i^{t_i}\)
注意到当存在 \(t_i>1\) 时,\(\mu(k)=0\),对答案无贡献,于是我们只需考虑指数为 \(0\) 或 \(1\) 的情况;同时我们还可以得到一个推论:\(f(\prod\limits_{i=1}^{m}p_i^{c_i})=f(\prod\limits_{i=1}^{m}p_i)\),其中 \(c_i\ge 1\)。
我们对 \(m\) 进行分讨:
\(m=0\)
此时 \(T=1\),有 \(f(T)=1\)。
\(m=1\)
此时 \(f(p^\alpha)=f(p)=p^{\mu(p)}=p^{-1}\)
\(m>1\)
我们把 \(f(T)\) 想象成一个分数。
我们我们发现,当指数 \(\mu\) 为 \(1\) 时,这个因数 \(k\) 出现在分子上;否则出现在分母上。
于是我们考虑某一个质因数 \(p\) 分别出现在分子和分母上的数量。
既然 \(p\) 要出现,那么 \(p\) 必须得选,在剩下的 \(m-1\) 个因子中选择一些。
所以分子上的个数就是 \(0+C_{m-1}^{1}+C_{m-1}^{3}+\dots\)
同理,分母上出现的个数是 \(C_{m-1}^{0}+C_{m-1}^{2}+\dots\)
根据一点点组合数学的知识,我们会发现无论 \(m\) 的奇偶性,这俩玩意肯定相等!
既然对于每个质因子,上下的出现次数都相等,那么这一坨玩意就是 \(1\)!
即 \(f(T)=1\)。
综上所述,我们发现分子绝对是 \(1\),而分母是这么个玩意,两者一除,就得到了我们开篇提到的神奇函数 \(g\)!
所以原式就变成了:
预处理 \(g\) 的前缀积和其逆元,这个就可以一层数论分块搞定!!!
\(type=0\)
将 \(f(0)=1\) 带入并稍做处理,得:
1.分子
把 \(i\) 提出来(求乘积,故加上 \(B\) 次方):
发现内层循环就是 \(B!\) ,即:
\(B!\) 和 \(i\) 无关,于是分离开来(记得加上 \(A\) 次方):
观察左边的式子,由于是求乘积,可以把 \(B\) 次方提出来,之后里面就成了 \(A!\) ,即:
很优美了,预处理阶乘后快速幂就行。
2.分母
发现两项非常相似,于是我们令:
此时分母就成了:
而 \(f_0\) 里面的东西开篇就推过了,所以有:
虽然我有统统带回去的毛病,但懒了,不把原式子放出来了!
这样这部分就做完了!
\(type=1\)
将 \(f(1)=ijk\) 带入,得:
分子
把关于 \(k\) 的一维扔出去:
发现在 \(k\) 变化时,内部不变,且外层循环的含义就是 \(\sum\limits_{k=1}^Ck\) 个内部的东西乘起来,于是可以变形:
求和公式套进去:
接下来,我们先处理内部:
把幂放进去:
把 \(i^{ij}\) 和 \(j^{ij}\) 分开计算(为了使结构更相似,将后面那部分把 \(i\) 和 \(j\) 互换名称):
两部分结构又相同了,我们考虑其中一个:
可以发现,在 \(j\) 变化时,\(i^i\) 不变,这还是求乘积,可以把关于 \(j\) 的一维扔到指数上:
指数就成了求和公式,这玩意我们熟悉的不得了,于是:
然后一层层往回带,分子部分就变成了:
预处理 \(i^i\) 的前缀和,直接算就行了。
分母
发现两项非常相似,于是我们令:
此时分母就成了:
接下来处理 \(f_1\),先把无关紧要的 \(k\) 提出去,通过和处理分子相同的方法,发现变成了:
接下来套路处理内部就行了,枚举 \(\gcd\):
莫反拆开,枚举 \(k\):
把 \(i\) 和 \(j\) 的循环拆开,发现是等差数列求和,于是:
预处理一下 \(d^{d^2}\) 的前缀积,和 \(\mu(k)k^2\) 的前缀和,两层数论分块做就完事了。但是,我们有更优的做法!
继续推,把指数上的求和放下来,有:
枚举 \(T=dk\),有:
也就是:
我们找到老熟人了,里面的东西其实就是 \(g(T)^{T^2}\)!
于是我们预处理这个东西的前缀积和其逆元,就有:
一次数论分块即可。
\(type=2\)
把 \(f(2)=\gcd(i,j,k)\) 带入,得:
我们处理分子分母吧……好累啊……
分子
我们记:
则分子就是 \(f_2(a,b,c) \times f_2(b,a,c)\)
我们推 \(f_2\):
大头敲开方向要对鲜美鸡汤必须喝掉
枚举\(\gcd\)莫反拆开枚举\(dk\)后数论分块
蛮好的,有 \(\varphi\) 了:
\(i\) 和 \(T\) 分开,有:
一次数论分块即可。
分母
我们令:
那么分母就是:\(f_3(A,B,C) \times f_3(A,C,B)\)
推 \(f_3\) 即可,这东西折磨了我好久,直到我把两次拆开放到一起……
枚举 \(d=\gcd(i,j,k)\),有:
拆成 \(\mu\),放到底数:
\(t\) 移出去:
发现 \(k\) 这一维可以缩水到指数上了,顺便把变量名 \(t\) 换成 \(k\),有:
然后我们枚举 \(T=dk\),有:
得,\(\varphi\) 出来了,这一步我退出来乐死了。
分成两部分,就是:
后者,又出现了我们文章最开头推的式子,于是就成了:
两层数论分块,终于,搞!定!
这b题终于让我写!!完!!了!!
点击查看代码
//#pragma GCC optimize(2)
//#pragma GCC optimize(3)
//#pragma GCC optimize("Ofast","inline")
#include<bits/stdc++.h>
#define fr(a) freopen(a,"r",stdin)
#define fw(a) freopen(a,"w",stdout)
#define MP(a,b) make_pair(a,b)
#define DEBUG
using namespace std;
typedef long long ll;
const int N=1e5+10;
ll T=1,mod,pmod;
ll A,B,C,ans1,ans2,ans3;
ll prime[N],v[N],tot,mu[N],smu[N],smu2[N],phi[N];
ll jie[N],invj[N],tphi[N],invtphi[N],sphi[N];
ll slf[N],invslf[N];
ll gm[N],invgm[N],invsmu[N];
ll nai[N],sn[N],invsn[N],sn2[N],invsn2[N];
ll qpow(ll a,ll n){
n=(n%pmod +pmod)%pmod;
a %=mod;
ll res=1;
while(n){
if(n&1) res=(res*a)%mod;
a=(a*a)%mod;n>>=1;
}return res;
}
void shai(ll n){
mu[1]=phi[1]=1;
for(ll i=2;i<=n;i++){
if(v[i]==0){
v[i]=i;prime[++tot]=i;
mu[i]=-1;phi[i]=i-1;
}for(ll j=1;j<=tot;j++){
if(prime[j]>v[i]||prime[j]>n/i) break;
v[i*prime[j]]=prime[j];
if(i%prime[j]==0) mu[i*prime[j]]=0,phi[i*prime[j]]=phi[i]*(prime[j]);
else mu[i*prime[j]]=-mu[i],phi[i*prime[j]]=phi[i]*(prime[j]-1);
}
}
}
void init(ll n){
jie[0]=invj[0]=slf[0]=invslf[0]=1;
gm[0]=invgm[0]=1;
tphi[0]=invtphi[0]=1;
pmod=mod-1;
for(ll i=1;i<=n;i++){
jie[i]=(jie[i-1]*i)%mod;
invj[i]=qpow(jie[i],mod-2);
slf[i]=slf[i-1] * qpow(i,i)%mod;
invslf[i]=qpow(slf[i],mod-2);
smu[i]=(smu[i-1]+mu[i])%pmod;
smu2[i]=(smu2[i-1] + mu[i] * i * i%pmod)%pmod;
gm[i]=gm[i-1] * qpow(i,mu[i])%mod;
invgm[i]=qpow(gm[i],-1);
tphi[i]=tphi[i-1] * qpow(i,phi[i]) %mod;
invtphi[i]=qpow(tphi[i],-1);
sphi[i] = (sphi[i-1] + phi[i]) %pmod;
nai[i]=1;
}
for(int i=1;i<=tot;i++){
ll now=prime[i];
if(now>n) break;
while(now<n){
nai[now]=prime[i];
now *= prime[i];
}
}
sn[0]=invsn[0]=sn2[0]=invsn2[0]=1;
for(int i=1;i<=n;i++){
sn[i]=(sn[i-1] * nai[i])%mod;
invsn[i]=qpow(sn[i],-1);
sn2[i]= sn2[i-1] * qpow(nai[i],1ll * i*i%pmod) %mod;
invsn2[i]=qpow(sn2[i],-1);
}
}
ll f32(ll a,ll b){
ll res=1,lim=min(a,b);
for(ll l=1,r;l<=lim;l=r+1){
ll ad=a/l,bd=b/l;r=min(a/ad,b/bd);
(res *= qpow(sn[r] * invsn[l-1] %mod ,ad * bd%pmod) )%=mod;
}return res;
}
ll f0(ll a,ll b,ll c){
return qpow(f32(a,b),c);
}
void work1(){
ll ans=qpow(qpow(jie[A],B)*qpow(jie[B],A)%mod,C),tot=1;
(ans*=qpow(f0(A,B,C) * f0(A,C,B) %mod,mod-2)) %=mod;
ans1=(ans+mod)%mod;
}
ll f1(ll a,ll b,ll c){
ll res=1,lim=min(a,b);
for(ll l=1,r;l<=lim;l=r+1){
ll ad=a/l,bd=b/l;r=min(a/ad,b/bd);
ll zhi=(ad*(ad+1)/2) %pmod *( (bd*(bd+1)/2) %pmod) %pmod;
(res *= qpow(sn2[r] * invsn2[l-1] %mod ,zhi) )%=mod;
}
res= qpow(res,c*(c+1)/2);
return res;
}
void work2(){
ll zi=1,ans=qpow(f1(A,B,C) * f1(A,C,B)%mod,mod-2);
zi=qpow(slf[A],B*(B+1)/2) * qpow(slf[B],A*(A+1)/2) %mod;
(ans *=qpow(zi,C*(C+1)/2)) %=mod;
ans2=(ans+mod)%mod;
}
ll f2(ll a,ll b,ll c){
ll res=1,limd=min(min(a,b),c);
for(ll ld=1,rd;ld<=limd;ld=rd+1){
ll ad=a/ld,bd=b/ld,cd=c/ld;
rd=min(min(a/ad,b/bd),c/cd);
(res *= qpow(gm[rd] * invgm[ld-1]%mod,ad*bd%pmod*cd%pmod) )%=mod;
ll tmp=qpow(jie[ad],bd*cd%pmod);
(res *= qpow(tmp,(smu[rd]-smu[ld-1])%pmod))%=mod;
}return res;
}
ll f2_5(ll a,ll b,ll c){
ll res=1,lim=min(min(a,b),c);
for(ll l=1,r;l<=lim;l=r+1){
ll at=a/l,bt=b/l,ct=c/l;
r=min(min(a/at,b/bt),c/ct);
(res *= qpow(tphi[r] * invtphi[l-1]%mod , at * bt %pmod *ct %pmod))%=mod;
ll tmp=qpow(jie[at],bt*ct%pmod);
(res *= qpow(tmp,(sphi[r] - sphi[l-1]) %pmod))%=mod;
}return res;
}
ll f3(ll a,ll b,ll c){
ll limt=min(min(a,b),c),res=1;
for(int lt=1,rt;lt<=limt;lt=rt+1){
ll at=a/lt,bt=b/lt,ct=c/lt;
rt=min(min(a/at,b/bt),c/ct);
ll tmp=tphi[rt] * invtphi[lt-1] %mod;
(res *= qpow(tmp,at * bt%pmod * ct%pmod) )%=mod;
tmp=qpow(f32(at,bt) %mod,ct) %mod;
(res *= qpow(tmp,(sphi[rt] - sphi[lt-1]) %pmod) )%=mod;
}return res;
}
void work3(){
ll zi=f2_5(A,B,C) * f2_5(B,A,C) %mod;
ll mu=f3(A,B,C) * f3(A,C,B)%mod;
ans3=(zi*qpow(mu,-1)%mod+mod)%mod;
return;
}
int main(){
scanf("%lld%lld",&T,&mod);
shai(N-10);
init(N-10);
for(int Case=1;Case<=T;Case++){
scanf("%lld%lld%lld",&A,&B,&C);
work1();work2();
work3();
printf("%lld %lld %lld\n",ans1,ans2,ans3);
}
return 0;
}
- P1587 [NOI2016] 循环之美
给定 \(n,m,k\),求分子不大于 \(n\),分母不大于 \(m\),在 \(k\) 进制下为纯循环小数的最简分数个数。
我们有一个不会证的结论:当分母和进制互质,分子和分母互质时,这个分数是 \(k\) 进制下的纯循环小数。
于是就成了求以下东西:
推式子,对后一项莫反拆开:
显然,我们有 \(\gcd(a,b)=1,\gcd(a,c)=1\Longleftrightarrow\gcd(a,bc)=1\)
故我们发现后面是一个规模缩小的原问题。
我们设:
于是我们有递推式:
直接开搜就行了。
终止状态:
-
当 \(n=0\) 或 \(m=0\) 时,\(f(n,m,k)=0\)。
-
当 \(k=1\) 时,\(f(n,m,k)=\sum\limits_{d=1}^{\min(n,m)}\mu(d)\left\lfloor\dfrac{n}{d}\right\rfloor\left\lfloor\dfrac{m}{d}\right\rfloor\),基本推式子没的说。
所以我们用杜教筛处理 \(\mu\) 前缀和,数论分块算上面的东西,并加入以下剪枝:
-
我们提前预处理 \(k\) 的“所有因数”的所有因数,显然空间复杂度是 \(O(k^2)\) 的,可以接受,此时枚举 \(d\) 的复杂度直接从 \(1000\) 降到了 \(64\),很大的优化。
-
当 \(\mu(d)=0\) 时,不往下搜,显然,这个优化更大。
加入这两个剪枝后就能顺利过此题了。
特别鸣鸣鸣鸣鸣鸣鸣鸣谢 Qcfff,基本上思路全都是抄他的呜呜呜。
P3704 [SDOI2017] 数字表格
给定 \(n,m\),求:
这不乱做?
枚举 \(T=dk\):
里面的东西预处理,\(O(n\sqrt n)\),因为指数只会是 \(0,1,-1\)。
然后做完了!整除分块。水紫真舒服
P2257 YY的GCD
求:
莫个反先:
枚举 \(T=pd\):
后面的可以预处理。求个前缀和。做完了。
P1891 疯狂 LCM
挺好玩。求:
先拆一下:
枚举 \(\gcd\):
发现好玩的事:\(d\) 约掉了。我们记 \(f(n)=\sum\limits_{i=1}^{n} i[\gcd(i,n)=1]\),于是原式变成:
我们先处理 \(f(n)\):莫反拆开:
枚举 \(k\):
也就是:
然后 \(f\) 可以 \(O(n\log n)\) 预处理吧!我们再看原式:
也可以 \(O(n\log n)\) 预处理吧!
于是我们 \(O(n\log n)\) 预处理,\(O(1)\) 查询。做完了!