前置知识
数论分块
P2261 [CQOI2007] 余数求和
:::info[代码]
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,l,r,k,ans;
signed main()
{cin>>n>>k;while(1){l=r+1;r=k/(k/l);if(r>k)r=k;if(r>n)r=n;ans+=(l+r)*(k/l)*(r-l+1)/2;if(r==k)break;if(r==n)break;}ans=n*k-ans;cout<<ans;return 0;
}
:::
积性函数
定义&性质
常见积性函数
解释:
莫比乌斯函数
线性筛求法
:::info[代码]
mu[1]=1;for(int i=2;i<=n;i++){if(!book[i]){prime[++pnum]=i;mu[i]=-1;}for(int j=1;j<=pnum&&i*prime[j]<=n;j++){book[i*prime[j]]=1;if(i%prime[j]==0){mu[i*prime[j]]=0;break;}mu[i*prime[j]]=-mu[i];}}
:::
狄利克雷卷积
定义
若函数 \(f,g,h\) 满足:
则称 \(f\) 为 \(g,h\) 的狄利克雷卷积,记作 \(f=g*h\)
举例
莫比乌斯反演核心函数
反演
公式一:GCD的处理
:::info[公式]
:::
公式二:整除 \(\Sigma\) 外提
:::info[公式]
:::
公式三:外提因子
:::info[公式]
:::
公式四:欧拉反演
:::info[公式]
:::
例题
P1390 公约数的和
题解
:::info[推导]
设 \(T=dp\),则
:::
std
:::info[代码]
#include <bits/stdc++.h>
#define int long long
using namespace std;
int book[2000005],prime[1000005],pnum,phi[2000005],n;
int ans[2000005];
signed main()
{cin>>n;phi[1]=1;for(int i=2;i<=n;i++){if(!book[i]){prime[++pnum]=i;phi[i]=i-1;}for(int j=1;j<=pnum&&i*prime[j]<=n;j++){book[i*prime[j]]=1;if(i%prime[j]==0){phi[i*prime[j]]=phi[i]*prime[j];break;}phi[i*prime[j]]=phi[i]*phi[prime[j]];}}for(int i=1;i<=n;i++)ans[i]=ans[i-1]+phi[i];for(int i=1;i<=n;i++)ans[i]=ans[i]*2-1;int ret=0;for(int i=1;i<=n;i++){ret+=i*ans[n/i];}cout<<(ret-n*(n+1)/2)/2;return 0;
}
:::
P3327 [SDOI2015] 约数个数和
题解
:::info[推导]
我们只需数论分块预处理出 $ \displaystyle
\sum^{\lfloor\frac n p\rfloor}{k=1}
\lfloor\frac n {kp}\rfloor \(
与
\) \displaystyle\sum^{\lfloor\frac m p\rfloor}
\lfloor\frac m {lp}\rfloor
$ 即可。
:::
std
:::info[代码]
#include<bits/stdc++.h>
using namespace std;
int mu[100005],prime[100005],pnum,book[100005],s[100005];
long long clac(int n){int l=0,r=0;long long ret=0;for(;;){if(r>=n)break;l=r+1;r=n/(n/l);r=min(r,n);ret+=(r-l+1)*(n/l);}return ret;
}
long long f(int n,int m){int l=0,r=0;long long ret=0;for(;;){if(r>=n||r>=m)break;l=r+1;r=min(n/(n/l),m/(m/l));r=min(r,n);r=min(r,m);ret+=(long long)(mu[r]-mu[l-1])*s[n/l]*s[m/l];}return ret;
}
int main(){mu[1]=1;for(int i=1;i<=50000;i++){s[i]=clac(i);}for(int i=2;i<=100000;i++){if(!book[i]){prime[++pnum]=i;mu[i]=-1;}for(int j=1;j<=pnum&&i*prime[j]<=100000;j++){book[i*prime[j]]=1;if(i%prime[j]==0){mu[i*prime[j]]=0;break;}mu[i*prime[j]]=-mu[i];}}prime[pnum+1]=0x3f3f3f3f;for(int i=2;i<=100000;i++){mu[i]+=mu[i-1];}int T;cin>>T;while(T--){int a,b;long long ans=0;cin>>a>>b;printf("%lld\n",f(a,b));}return 0;
}
:::
P2257 YY的GCD
题解
:::info[推导]
设 \(kp=T\),则:
:::
P4980 【模板】Pólya 定理
一切空间、平面、群等数学名词,都是纸老虎满足特定性质的集合。
题解:
:::info[推导]
题意:求
:::
std
:::info[代码]
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int MX=3000000,mod=1e9+7;
int qpow(int x,int y){if(y==0)return 1;if(y==1)return x;int z=qpow(x,y/2);z=z*z%mod;if(y&1)z=z*x%mod;return z;
}
int phi(int x){int ret=x;for(int i=2;i*i<=x;i++){if(x%i==0){while(x%i==0)x/=i;ret=ret/i*(i-1);}}if(x>1)ret=ret/x*(x-1);return ret;
}
signed main(){int T,n;T,n,cin>>T;while(T--){int n;cin>>n;int c=sqrt(n);int ans=0;for(int i=1;i<=c;i++){if(n%i==0){ans+=qpow(n,i-1)*phi(n/i)%mod;int j=n/i;if(j!=i)ans+=qpow(n,j-1)*phi(n/j)%mod;ans%=mod;}}cout<<ans<<endl;}return 0;
}
:::
T536265 GLOI-买项链
题解
板子题(洛谷上的 AHOI 懒得写高精
代码
:::info[代码]
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int mod=998244353;
int n,k,a[1005][1005],b[1005][1005],c[1005][1005],d[1005][1005],cnt,ans;
int qpow(int x,int y){if(y==0)return 1;if(y==1)return x;int z=qpow(x,y/2);z=z*z%mod;if(y&1)z=z*x%mod;return z;
}
bool pd(){for(int i=1;i<=n;i++){for(int j=1;j<=n;j++){if(a[i][j]!=b[i][j])return 0;}}return 1;
}
bool pd2(){for(int i=1;i<=n;i++){for(int j=1;j<=n;j++){if(a[i][j]!=c[i][j])return 0;}}return 1;
}signed main() {cnt=0;ans=0;
// scanf("%lld%lld",&n,&k);cin>>n>>k;int bl=0;for(int i=1;i<=n;i++){for(int j=1;j<=n;j++){scanf("%d",&a[i][j]);if(a[i][j])bl++;}}for(int j=n,k=1;j>=1;j--,k++){for(int i=1;i<=n;i++){d[k][i]=a[i][j];}}for(int i=1;i<=n;i++){for(int j=1;j<=n;j++){c[i][j]=a[n-i+1][n-j+1];}}for(int j=1;j<=n;j++){for(int i=n,k=1;i>=1;i--,k++){b[j][k]=a[i][j];}}if(pd()){ans+=2*qpow(k,(bl+3)/4);cnt+=2;}if(pd2()){ans+=qpow(k,(bl+1)/2);cnt++;}ans+=qpow(k,bl);cnt++;ans%=mod;printf("%lld",ans*qpow(cnt,mod-2)%mod);
// puts("");
// for(int i=1;i<=n;i++){
// for(int j=1;j<=n;j++)
// printf("%d ",b[i][j]);
// puts("");
// }
// puts("");
// for(int i=1;i<=n;i++){
// for(int j=1;j<=n;j++)
// printf("%d ",c[i][j]);
// puts("");
// }
// puts("");
// for(int i=1;i<=n;i++){
// for(int j=1;j<=n;j++)
// printf("%d ",d[i][j]);
// puts("");
// }return 0;
}/*
11 11
1 0 1 0 1 0 1 0 1 0 1
0 1 0 1 0 1 0 1 0 1 0
1 0 1 0 1 0 1 0 1 0 1
0 1 0 1 0 1 0 1 0 1 0
1 0 1 0 1 0 1 0 1 0 1
0 1 0 1 0 1 0 1 0 1 0
1 0 1 0 1 0 1 0 1 0 1
0 1 0 1 0 1 0 1 0 1 0
1 0 1 0 1 0 1 0 1 0 1
0 1 0 1 0 1 0 1 0 1 0
1 0 1 0 1 0 1 0 1 0 1
*/
:::
P4727 [HNOI2009] 图的同构计数
题解
:::info[推导]
有意思的题++
考虑将题意转化为完全图的边染成 \(2\) 种颜色(存在或不存在)。则可以构造出置换群:
其中 \(b\) 为 \(1\sim\frac{n(n-1)}{2}\) 的一个排列。
对于当前排列,如何计算方案数?
直接上 Pólya 时间复杂度爆炸,所以这里有一个奇怪(?)的套路:枚举该群的子群长度,子群长度都相等的置换群贡献相同。
如 \(n=4\) 时,群长度为 \(6\),就可以分为 \((1,1,1,1,1,1),(2,1,1,1,1),(2,2,1,1),(2,2,2),(3,1,1,1),(3,2,1),(3,3),(4,1,1),(4,2),(5,1),(6)\) 这 \(11\) 种排列。设排列 \((b_1,b_2,\dots,b_k)\),则易得排列的重复数为 \(\prod b\),但会有相同的 \(b\),所以答案还需除以 \(\prod p!\)(\(p_i\) 表示 \(i\) 在 \(b\) 中出现次数)。
但会出现一个 Venti 问题:时间复杂度仍逼近 \(O(n^2!)\)(虽然常数极小)。
我们尝试更改置换群
\(\begin{pmatrix}
b_1\ b_2\dots b_k
\end{pmatrix}\)
使其表示点的状态。
如何统计边的状态数呢?
我们考虑把边分成两类:
两端点在同一置换内
由于相邻的点都等价,两边等价当且仅当两端点在集合中的距离不同(想象将集合排成一个圆),所以集合内的边对状态的贡献为 \(\sum\lfloor\frac{b_i}{2}\rfloor\)
两端点在不同置换内
由于相邻的点都等价,我们可以想象一次置换是“旋转”置换群一次,那么旋转之后和原边相等的边自然就有贡献。我们来一组群:
会发现边 \(1,1\) 旋转之后会变成 \(3,4\),等价于边 \(1,2\),再旋转得 \(1,3\),再得 \(1,1\),即每一条边都为相同置换。
那怎么统计呢?我们再来一组群:
会发现边 \(1,1\) 旋转之后会变成 \(4,6\),等价于边 \(1,3\),在旋转就变成 \(1,5\),在旋转变会 \(1,1\),会发现置换集合大小只有 \(3\),即有 \(2\) 种置换。
通过这两组数据,我们会发现,旋转加的数为 \(\gcd(b_i,b_j)\),这也是置换的数量。(可以 CRT 证明)
于是群之间的贡献为 \(\sum^m_{i=1}\sum^{i-1}_{j=1}\gcd(b_i,b_j)\),解决!
最后具体做法是枚举 \(b\) 数组,答案加上
需要预处理 GCD,否则会被卡常QAQ
:::
代码
:::info[代码]
#include<bits/stdc++.h>
using namespace std;
#define ls (id * 2)
#define rs (id * 2 + 1)
#define fi first
#define se second
typedef long long ll;
typedef unsigned long long ull;
typedef long double ld;
int n,d[105],mu,b[105],mul[105],GCD[105][105];
ll ans;
const int mod=997;
ll qpow(ll x,ll y){if(y==1)return x;if(y==0)return 1;ll z=qpow(x,y/2);z=z*z%mod;if(y&1)z=z*x%mod;return z;
}
ll gcd(ll a,ll b){if(b==0)return a;return gcd(b,a%b);
}
ll inv(ll x){return qpow(x,mod-2);
}
void Polya(int m){ll ret=0,r2=1,r3=1;memset(b,0,sizeof(b));for(int i=1;i<=m;i++){
// printf("%d ",d[i]);r2=r2*d[i]%mod;b[d[i]]++;ret+=d[i]/2;}for(int i=1;i<=60;i++){r3=r3*mul[b[i]]%mod;}for(int i=1;i<=m;i++){for(int j=1;j<i;j++){ret+=GCD[d[i]][d[j]];}}ans=(ans+qpow(2,ret)*inv(r2)%mod*inv(r3))%mod;
}
void dfs(int x,int sum,int minn){if(sum==0){Polya(x-1);return;}
// if(minn*x>sum)return;for(int i=1;i<=minn&&i<=sum;i++){d[x]=i;dfs(x+1,sum-i,i);}
}
int main() {cin>>n;mul[0]=1;for(int i=1;i<=n;i++)mul[i]=mul[i-1]*i%mod;for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)GCD[i][j]=gcd(i,j);dfs(1,n,n);cout<<ans%mod;return 0;
}
:::
拓展:杜教筛
理论部分:
杜教筛,顾名思义,是由杜教(杜瑜皓)dalao 首次应用在中国 OI/ACM 竞赛界的。
杜教筛是用于在小于 \(O(n)\) 的时间内求积性函数前缀和的一种邪道算法。
具体的,对于
$ \displaystyle\sum^{n}{i=1}f(i)$
杜教筛会构造两个函数 \(g,h\) 使得 \(f*g=h\),再使用莫比乌斯反演与狄利克雷卷积的一些公式或性质,推出形如 $ \displaystyle s(n)=\sum^nc(i)-s(\lfloor\frac{n}{i}\rfloor)$ 的递推式。(其中 \(\displaystyle s(n)=\sum^{n}_{i=1}f(i)\),\(c(i)\) 为可以 \(O(1)\) 求解的线性函数)
P4213 【模板】杜教筛
题解
:::info[推导]
对于这道题,我们需要构造 \(\mu*g=h\) 以及 \(\varphi*g=h\)。如何构造 \(g\) 与 \(h\) 呢?
跳转至上文“莫比乌斯反演核心函数”部分,我们看到这两个公式:
然后构造就完成了。
求 \(\mu\) 函数前缀和
记 \(\displaystyle S(n)=\sum^n_{i=1}\mu(i)\)。
因为 \(\mu*1=\varepsilon\)
所以 \(\displaystyle \sum^n_{i=1}\varepsilon(i)=\sum^n_{i=1}\sum_{j|i}\mu(j)\)
我们将 \(j=1\) 一项提取出来,得:
移项,得:
求 \(\varphi\) 函数前缀和
记 \(\displaystyle S(n)=\sum^n_{i=1}\varphi(i)\)。
因为 \(\varphi*1=id\)
所以 \(\displaystyle \sum^n_{i=1}id(i)=\sum^n_{i=1}\sum_{j|i}\varphi(j)\)
我们将 \(j=1\) 一项提取出来,得:
移项,得:
:::
std
:::info[代码]
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int MX=3000000;
int mu[MX+5],phi[MX+5],book[MX+5],prime[MX+5],pnum;
unordered_map<int,int> mm,mp;
int summu(int x){if(x<=MX)return mu[x];if(mm[x])return mm[x];int l=1,r=1,ret=1;while(1){l=r+1,r=x/(x/l);if(r>x)r=x;ret-=(r-l+1)*summu(x/l);if(r==x)break;}mm[x]=ret;return ret;
}
int sumphi(int x){if(x<=MX)return phi[x];if(mp[x])return mp[x];int l=1,r=1,ret=x*(x+1)/2;while(1){l=r+1,r=x/(x/l);if(r>x)r=x;ret-=(r-l+1)*sumphi(x/l);if(r==x)break;}mp[x]=ret;return ret;
}
signed main(){int T,n;T,n,cin>>T;mu[1]=1;phi[1]=1;for(int i=2;i<=MX;i++){if(!book[i]){prime[++pnum]=i;mu[i]=-1;phi[i]=i-1;}for(int j=1;j<=pnum&&i*prime[j]<=MX;j++){book[i*prime[j]]=1;if(i%prime[j]==0){mu[i*prime[j]]=0;phi[i*prime[j]]=phi[i]*prime[j];break;}mu[i*prime[j]]=-mu[i];phi[i*prime[j]]=phi[i]*phi[prime[j]];}}for(int i=2;i<=MX;i++){mu[i]+=mu[i-1];phi[i]+=phi[i-1];}while(T--){cin>>n;printf("%lld %lld\n",sumphi(n),summu(n)); }return 0;
}
:::
P6055 [RC-02] GCD
题解
:::info[推导]
很巧妙的转换消元。
然后套杜教筛+数论分块就可以了~
:::
std
:::info[代码]
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int MX=3000000;
const int modx=998244353;
int mu[MX+5],book[MX+5],prime[MX+5],pnum;
unordered_map<int,int> mm;
int summu(int x){if(x<=MX)return mu[x];if(mm[x])return mm[x];int l=1,r=1,ret=1;while(1){l=r+1,r=x/(x/l);if(r>x)r=x;ret-=(r-l+1)*summu(x/l);if(r==x)break;}mm[x]=ret;return ret;
}
int getans(int x){int l=0,r=0,ret=0;while(1){l=r+1;r=x/(x/l);r=min(r,x);int c=summu(r)-summu(l-1);c%=modx;c*=x/l;c%=modx;c*=x/l;c%=modx;c*=x/l;c%=modx;ret=(ret+c)%modx;if(r==x)break;}return ret;
}
signed main(){int n;mu[1]=1;for(int i=2;i<=MX;i++){if(!book[i]){prime[++pnum]=i;mu[i]=-1;}for(int j=1;j<=pnum&&i*prime[j]<=MX;j++){book[i*prime[j]]=1;if(i%prime[j]==0){mu[i*prime[j]]=0;break;}mu[i*prime[j]]=-mu[i];}}for(int i=2;i<=MX;i++){mu[i]+=mu[i-1];}cin>>n;cout<<(getans(n)+modx)%modx; return 0;
}
:::
P3768 简单的数学题
题解
:::info[推导]
其中
感觉有题解做复杂了呢,还化成了 \(\mu\)。
数论分块即可……等等,\(n\le10^{10}\)?
也就是说,我们要在小于 \(O(n)\) 的复杂度内求出所有 \(p^2\varphi(p)\)。
考虑杜教筛。
设 \(f(x)=x^2\varphi(x)\),有函数 \(g,h\),满足 \(f*g=h\)。
那么,有:
这个 \(p^2\) 不好处理 ,我们考虑把它消掉。
注意到,\(p\times\frac{n}{p}=n\)。
于是,我们考虑再设 \(g'(x)=x^2g'(x)\)
我们考虑设 \(h'(x)=x^2h(x)\)
此时,我们易得:
也就是原来的:
于是,就可以杜教筛了。
我们设 \(S(x)=\sum_{i=1}^x f(x)\)
完成!
但是这要做 \(O(\sqrt n)\) 次杜教筛,不会TLE吗?
冷知识:一次杜教筛不止筛一个数。实际上,它会把每个 \(\frac{x}{p}\) 都筛到,也就是说,我们只需要筛一遍,就可以找出所有需要的 \(S\) 了。
:::
std
:::info[代码]
#include<bits/stdc++.h>
#define int __int128
#define _2(x) ((x)*(x)%mod)
using namespace std;
typedef long long ll;
const int MX=5000000;
ll mod,inv6;
int mu[MX+5],phi[MX+5],book[MX+5],prime[MX+5],pnum;
unordered_map<ll,ll>mp;
int qpow(int x,int y){if(y==0)return 1;int z=qpow(x,y/2);z=z*z%mod;if(y&1)z=z*x%mod;return z;
}
int inv(int x){return qpow(x,mod-2);
}
int sum(__int128 x){return _2(x*(x+1)/2%mod);
}
int sum2(__int128 x){return (x*(x+1)%mod*(2*x+1)%mod*inv6%mod);
}
int sumphi(int x){if(x<=MX)return phi[x];if(mp[x])return mp[x];int l=1,r=1,ret=x*(x+1)/2%mod;ret=ret*ret%mod;while(1){l=r+1,r=x/(x/l);if(r>x)r=x;ret-=(sum2(r)-sum2(l-1))*sumphi(x/l)%mod;ret%=mod;if(r==x)break;}ret+=mod;ret%=mod;mp[x]=ret;return ret;
}
ll calc(int x){int l=1,r=0,ret=0;sumphi(x);while(1){l=r+1,r=x/(x/l);if(r>x)r=x;ret=ret+(sumphi(r)-sumphi(l-1))*sum(x/l)%mod;ret%=mod;if(r==x)return (ret+mod)%mod;}
}
signed main(){int T;ll n;cin>>mod;inv6=inv(6);mu[1]=1;phi[1]=1;for(int i=2;i<=MX;i++){if(!book[i]){prime[++pnum]=i;mu[i]=-1;phi[i]=i-1;}for(int j=1;j<=pnum&&i*prime[j]<=MX;j++){book[i*prime[j]]=1;if(i%prime[j]==0){mu[i*prime[j]]=0;phi[i*prime[j]]=phi[i]*prime[j];break;}mu[i*prime[j]]=-mu[i];phi[i*prime[j]]=phi[i]*phi[prime[j]];}}for(int i=2;i<=MX;i++){mu[i]+=mu[i-1];phi[i]=(phi[i-1]+i*i%mod*phi[i]%mod)%mod;}cin>>n;printf("%lld\n",calc(n)); return 0;
}
:::
P9238 [蓝桥杯 2023 省 A] 翻转硬币
题解
:::info[题解]
观察得:答案是 \(\sum^{n}_{i=1}|\mu(i)|\)。
继续观察,容斥,答案是 \(\sum^{\sqrt n}_{i=1}\mu(i)\frac{n}{i^2}\)。
注意到,这个可以数论分块,再加杜教筛处理 \(\mu\) 和即可通过本题。
:::
std
:::info[代码]
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int MX=10000000;
int mu[MX+5],phi[MX+5],book[MX+5],prime[MX+5],pnum;
unordered_map<int,int> mm,mp;
int summu(int x){if(x==0)return 0;if(x<=MX)return mu[x];if(mm[x])return mm[x];int l=1,r=1,ret=1;while(1){l=r+1,r=x/(x/l);if(r>x)r=x;ret-=(r-l+1)*summu(x/l);if(r==x)break;}mm[x]=ret;return ret;
}
int sumphi(int x){if(x<=MX)return phi[x];if(mp[x])return mp[x];int l=1,r=1,ret=x*(x+1)/2;while(1){l=r+1,r=x/(x/l);if(r>x)r=x;ret-=(r-l+1)*sumphi(x/l);if(r==x)break;}mp[x]=ret;return ret;
}
int T,n;
int calc(){int l=0,r=0,ret=0,sq=sqrt(n);summu(sqrt(n));while(1){l=r+1;if(l*l>n)break;r=sqrt(n/(n/(l*l)));
// printf("%d %d %d %d\n",l,r,n/(l*l),n/(r*r));if(r*r>n)r=sq;ret+=(n/(l*l))*(summu(r)-summu(l-1));}return ret;
}
signed main(){mu[1]=1;phi[1]=1;for(int i=2;i<=MX;i++){if(!book[i]){prime[++pnum]=i;mu[i]=-1;phi[i]=i-1;}for(int j=1;j<=pnum&&i*prime[j]<=MX;j++){book[i*prime[j]]=1;if(i%prime[j]==0){mu[i*prime[j]]=0;phi[i*prime[j]]=phi[i]*prime[j];break;}mu[i*prime[j]]=-mu[i];phi[i*prime[j]]=phi[i]*phi[prime[j]];}}long long ans;cin>>n;for(int i=2;i<=MX;i++){mu[i]+=mu[i-1];phi[i]+=phi[i-1];}
// summu(sqrt(n));ans=0;
// for(long long i=1;i<=n;i++){
// if(mu[i])
// ans++;
// }
// for(long long i=1;i*i<=n;i++){
// ans+=mu[i]*(n/(i*i));
// }cout<<calc();return 0;
}
:::