【学习笔记】杜教筛

补充几个前置知识

莫比乌斯反演
φ ∗ 1 = i d \varphi*1=id φ∗1=id

杜教筛

杜教筛能够在 Θ ( n 2 3 ) \Theta(n^\frac{2}{3}) Θ(n32)求出一个积性函数的前缀和。

给出一个积性函数 f ( i ) f(i) f(i),求 ∑ i = 1 n f ( i ) \sum_{i=1}^{n}f(i) i=1∑nf(i)

构造一个前缀和容易求的积性函数 g g g,令 h = f ∗ g h=f*g h=f∗g(其中 ∗ * ∗是狄利克雷卷积),使得 h h h的前缀和也容易求,那么

∑ i = 1 n h ( i ) = ∑ i = 1 n ∑ d ∣ i g ( d ) f ( i d ) = ∑ d = 1 n g ( d ) ∑ i = 1 ⌊ n d ⌋ f ( i ) = ∑ d = 1 n g ( d ) S ( ⌊ n d ⌋ ) = g ( 1 ) S ( n ) + ∑ d = 2 n g ( d ) S ( ⌊ n d ⌋ ) \sum_{i=1}^{n}h(i) \\ =\sum_{i=1}^{n}\sum_{d|i}g(d)f(\frac{i}{d}) \\ =\sum_{d=1}^{n}g(d)\sum_{i=1}^{\lfloor\frac{n}{d}\rfloor}f(i) \\ =\sum_{d=1}^{n}g(d)S(\lfloor\frac{n}{d}\rfloor) \\ =g(1)S(n)+\sum_{d=2}^{n}g(d)S(\lfloor\frac{n}{d}\rfloor) i=1∑nh(i)=i=1∑nd∣i∑g(d)f(di)=d=1∑ng(d)i=1∑⌊dn⌋f(i)=d=1∑ng(d)S(⌊dn⌋)=g(1)S(n)+d=2∑ng(d)S(⌊dn⌋)

所以

g ( 1 ) S ( n ) = ∑ i = 1 n h ( i ) − ∑ d = 2 n g ( d ) S ( ⌊ n d ⌋ ) g(1)S(n)=\sum_{i=1}^{n}h(i)-\sum_{d=2}^{n}g(d)S(\lfloor\frac{n}{d}\rfloor) g(1)S(n)=i=1∑nh(i)−d=2∑ng(d)S(⌊dn⌋)

不难发现左边部分就是 h ( i ) h(i) h(i)的前缀和,右边可以通过整除分块做。

实现的时候需要先预处理前 Θ ( n 2 3 ) \Theta(n^\frac{2}{3}) Θ(n32)项,然后用map存下求出的已知项。时间复杂度 Θ ( n 2 3 ) \Theta(n^\frac{2}{3}) Θ(n32)(不会证qwq)

cpp 复制代码
int S(int n)
{
	if(n<=5000000) return sum[n];
	if(mp[n]!=0) return mp[n];
	int ans=g(n);
	for(int l=2,r; l<=n; l=r+1)
	{
		r=n/(n/l);
		(ans-=S(n/l)*(t(r)-t(l-1))%mod)%=mod;
	}
	return mp[n]=ans;
}

如何构造g?

我们的目的是让 h = f ∗ g h=f*g h=f∗g的前缀和也容易求,所以可以先把 ( f ∗ g ) ( n ) (f*g)(n) (f∗g)(n)写出来,看看 h ( n ) h(n) h(n)长啥样,努力凑出一个可以求前缀和的形式。

例题

【模板】杜教筛

题目描述

给定一个正整数,求

a n s 1 = ∑ i = 1 n φ ( i ) ans_1=\sum_{i=1}^n\varphi(i) ans1=i=1∑nφ(i)

a n s 2 = ∑ i = 1 n μ ( i ) ans_2=\sum_{i=1}^n \mu(i) ans2=i=1∑nμ(i)

输入格式

本题单测试点内有多组数据

输入的第一行为一个整数,表示数据组数 T T T。

接下来 T T T 行,每行一个整数 n n n,表示一组询问。

输出格式

对于每组询问,输出一行两个整数,分别代表 a n s 1 ans_1 ans1 和 a n s 2 ans_2 ans2。

样例 #1

样例输入 #1
复制代码
6
1
2
8
13
30
2333
样例输出 #1
复制代码
1 1
2 0
22 -2
58 -3
278 -3
1655470 2

提示

数据规模与约定

对于全部的测试点,保证 1 ≤ T ≤ 10 1 \leq T \leq 10 1≤T≤10, 1 ≤ n < 2 31 1 \leq n \lt 2^{31} 1≤n<231。

题解

莫比乌斯函数前缀和

μ ∗ 1 = ε μ∗1=ε μ∗1=ε

不难想到令 g = 1 g=1 g=1,那么 h = ε h=ε h=ε

所以式子就变成了
1 × S ( n ) = 1 − ∑ d = 2 n S ( ⌊ n d ⌋ ) 1\times S(n)=1-\sum_{d=2}^{n}S(\lfloor\frac{n}{d}\rfloor) 1×S(n)=1−d=2∑nS(⌊dn⌋)

欧拉函数前缀和

方法一 :根据定义用莫比乌斯反演做。
∑ i = 1 n φ ( i ) = ∑ i = 1 n ∑ j = i + 1 n [ g c d ( i , j ) = 1 ] \sum_{i=1}^{n}\varphi(i)=\sum_{i=1}^{n}\sum_{j=i+1}^{n}[gcd(i,j)=1] i=1∑nφ(i)=i=1∑nj=i+1∑n[gcd(i,j)=1]

这不就是莫比乌斯反演板子题吗

方法二 :杜教筛
φ ∗ 1 = i d \varphi*1=id φ∗1=id

令 g = 1 g=1 g=1得
S ( n ) = ∑ i = 1 n i − ∑ i = 2 n S ( ⌊ n d ⌋ ) S(n)=\sum_{i=1}^{n}i-\sum_{i=2}^nS(\lfloor\frac{n}{d}\rfloor) S(n)=i=1∑ni−i=2∑nS(⌊dn⌋)

代码

cpp 复制代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e6+7;
int mu[N],phi[N],s1[N],s2[N];
bool bz[N];
vector<int> p;
map<int,int> mp1,mp2;
void init(int n)
{
	mu[1]=1; phi[1]=1;
	for(int i=2; i<=n; i++)
	{
		if(!bz[i])
		{
			p.push_back(i);
			mu[i]=-1; phi[i]=i-1;
		}
		for(int j:p)
		{
			if(i*j>n) break;
			bz[i*j]=1;
			if(i%j==0)
			{
				phi[i*j]=phi[i]*j;
				break;
			}
			mu[i*j]=-mu[i];
			phi[i*j]=phi[i]*(j-1);
		}
	}
	for(int i=1; i<=n; i++)
	{
		s1[i]=mu[i]+s1[i-1];
		s2[i]=phi[i]+s2[i-1];
	}
}
int S1(int n)
{
	if(n<2) return n;
	if(n<=N-7) return s1[n];
	if(mp1[n]) return mp1[n];
	int ans=1;
	for(int l=2,r; l<=n; l=r+1)
	{
		r=n/(n/l);
		ans-=S1(n/l)*(r-l+1);
	}
	return mp1[n]=ans;
}
int S2(int n)
{
	if(n<2) return n;
	if(n<=N-7) return s2[n];
	if(mp2[n]) return mp2[n];
	int ans=n*(n+1)/2;
	for(int l=2,r; l<=n; l=r+1)
	{
		r=n/(n/l);
		ans-=S2(n/l)*(r-l+1);
	}
	return mp2[n]=ans;
}
void O_o()
{
	int n;
	cin>>n;
	cout<<S2(n)<<" "<<S1(n)<<"\n";
}
signed main()
{
	ios::sync_with_stdio(false);
	cin.tie(0); cout.tie(0);	
	int T=1;
	init(N-7);
	cin>>T;
	while(T--)
	{
		O_o();
	}
}

简单的数学题

题目描述

由于出题人懒得写背景了,题目还是简单一点好。

输入一个整数 n n n 和一个整数 p p p,你需要求出:

( ∑ i = 1 n ∑ j = 1 n i j gcd ⁡ ( i , j ) )   m o d   p \left(\sum_{i=1}^n\sum_{j=1}^n ij \gcd(i,j)\right) \bmod p (i=1∑nj=1∑nijgcd(i,j))modp

其中 gcd ⁡ ( a , b ) \gcd(a,b) gcd(a,b) 表示 a a a 与 b b b 的最大公约数。

输入格式

一行两个整数 p , n p,n p,n。

输出格式

一行一个整数表示答案。

样例 #1

样例输入 #1
复制代码
998244353 2000
样例输出 #1
复制代码
883968974

提示

对于20%的数据, n ≤ 1000 n \leq 1000 n≤1000。

对于30%的数据, n ≤ 5000 n \leq 5000 n≤5000。

对于60%的数据, n ≤ 1 0 6 n \leq 10^6 n≤106,时限1s。

对于另外20%的数据, n ≤ 1 0 9 n \leq 10^9 n≤109,时限3s。

对于最后20%的数据, n ≤ 1 0 10 n \leq 10^{10} n≤1010,时限4s。

对于100%的数据, 5 × 1 0 8 ≤ p ≤ 1.1 × 1 0 9 5 \times 10^8 \leq p \leq 1.1 \times 10^9 5×108≤p≤1.1×109 且 p p p 为质数。

题解

引入欧拉反演(用莫比乌斯反演能做,推出来是一样的,但是很麻烦)

∑ i = 1 n ∑ j = 1 n g c d ( i , j ) i j = ∑ i = 1 n ∑ j = 1 n i d ( g c d ( i , j ) ) i j = ∑ i = 1 n ∑ j = 1 n ∑ d ∣ g c d ( i , j ) φ ( d ) i j = ∑ d = 1 n φ ( d ) ∗ d 2 ∑ i = 1 ⌊ n d ⌋ ∑ j = 1 ⌊ n d ⌋ i j \sum_{i=1}^{n}\sum_{j=1}^{n}gcd(i,j)ij \\ =\sum_{i=1}^{n}\sum_{j=1}^{n}id(gcd(i,j))ij \\ =\sum_{i=1}^{n}\sum_{j=1}^{n}\sum_{d|gcd(i,j)}\varphi(d)ij \\ =\sum_{d=1}^{n}\varphi(d)*d^2\sum_{i=1}^{\lfloor\frac{n}{d}\rfloor}\sum_{j=1}^{\lfloor\frac{n}{d}\rfloor}ij i=1∑nj=1∑ngcd(i,j)ij=i=1∑nj=1∑nid(gcd(i,j))ij=i=1∑nj=1∑nd∣gcd(i,j)∑φ(d)ij=d=1∑nφ(d)∗d2i=1∑⌊dn⌋j=1∑⌊dn⌋ij

令 s u m ( n ) = ∑ i = 1 n ∑ j = 1 n i j sum(n)=\sum_{i=1}^{n}\sum_{j=1}^{n}ij sum(n)=∑i=1n∑j=1nij

显然 s u m ( n ) = n 2 ( n + 1 ) 2 4 sum(n)=\frac{n^2(n+1)^2}{4} sum(n)=4n2(n+1)2

令 f ( i ) = φ ( i ) ∗ i 2 f(i)=\varphi(i)*i^2 f(i)=φ(i)∗i2,现在我们如果能求出它的前缀和,我们就可以整除分块解决问题。这个时候就要杜教筛了:
( f ∗ g ) ( n ) = ∑ d ∣ n φ ( d ) ∗ d 2 ∗ g ( n d ) (f*g)(n)\\=\sum_{d|n}\varphi(d)*d^2*g(\frac{n}{d}) (f∗g)(n)=d∣n∑φ(d)∗d2∗g(dn)
d 2 d^2 d2特别烦人怎么办?消掉!

令 g ( i ) = i 2 g(i)=i^2 g(i)=i2
( f ∗ g ) ( n ) = ∑ d ∣ n φ ( d ) ∗ d 2 ∗ g ( n d ) = n 2 ∑ d ∣ n φ ( d ) = n 3 (f*g)(n)\\=\sum_{d|n}\varphi(d)*d^2*g(\frac{n}{d})\\=n^2\sum_{d|n}\varphi(d)\\=n^3 (f∗g)(n)=d∣n∑φ(d)∗d2∗g(dn)=n2d∣n∑φ(d)=n3

用我们小学二年级就学过的立方和公式得
∑ i = 1 n i 3 = n 2 ( n + 1 ) 2 4 = s u m ( n ) \sum_{i=1}^{n}i^3=\frac{n^2(n+1)^2}{4}=sum(n) i=1∑ni3=4n2(n+1)2=sum(n)

于是乎,整除分块即可

代码

cpp 复制代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=5e6+7;
int mod,inv6,phi[N],sum[N];
bool bz[N];
vector<int> p;
map<int,int> mp;
int power(int x,int t)
{
	int b=1;
	while(t)
	{
		if(t&1) b=b*x%mod;
		x=x*x%mod; t>>=1;
	}
	return b;
}
void init(int n)
{
	phi[1]=1;
	for(int i=2; i<=n; i++)
	{
		if(!bz[i])
		{
			p.push_back(i); phi[i]=i-1;
		}
		for(int j:p)
		{
			if(i*j>n) break;
			bz[i*j]=1;
			if(i%j==0)
			{
				phi[i*j]=phi[i]*j%mod;
				break;
			}
			phi[i*j]=phi[i]*phi[j]%mod;
		}
	}
	for(int i=1; i<=n; i++) sum[i]=(sum[i-1]+phi[i]*i%mod*i%mod)%mod;
}
int t(int n)
{
	n%=mod;
	return n*(n+1)%mod*(2*n%mod+1)%mod*inv6%mod;
}
int g(int n)
{
	n%=mod;
	return (n*(n+1)/2%mod)*(n*(n+1)/2%mod)%mod;
}
int S(int n)
{
	if(n<=5000000) return sum[n];
	if(mp[n]!=0) return mp[n];
	int ans=g(n);
	for(int l=2,r; l<=n; l=r+1)
	{
		r=n/(n/l);
		(ans-=S(n/l)*(t(r)-t(l-1))%mod)%=mod;
	}
	return mp[n]=ans;
}
void O_o()
{
	int n,ans=0;
	cin>>mod>>n;
	inv6=power(6,mod-2);
	init(5000000);
	for(int l=1,r; l<=n; l=r+1)
	{
		r=n/(n/l);
		(ans+=(S(r)-S(l-1))*g(n/l)%mod)%=mod;
	}
	cout<<(ans+mod)%mod;
}
signed main()
{
	ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
	cout<<fixed<<setprecision(2);
	int T=1;
	
//	cin>>T;
	while(T--)
	{
		O_o();
	}
}
相关推荐
知识分享小能手1 小时前
React学习教程,从入门到精通, React 属性(Props)语法知识点与案例详解(14)
前端·javascript·vue.js·学习·react.js·vue·react
luckys.one1 小时前
第9篇:Freqtrade量化交易之config.json 基础入门与初始化
javascript·数据库·python·mysql·算法·json·区块链
~|Bernard|3 小时前
在 PyCharm 里怎么“点鼠标”完成指令同样的运行操作
算法·conda
战术摸鱼大师3 小时前
电机控制(四)-级联PID控制器与参数整定(MATLAB&Simulink)
算法·matlab·运动控制·电机控制
Christo33 小时前
TFS-2018《On the convergence of the sparse possibilistic c-means algorithm》
人工智能·算法·机器学习·数据挖掘
汇能感知3 小时前
摄像头模块在运动相机中的特殊应用
经验分享·笔记·科技
阿巴Jun3 小时前
【数学】线性代数知识点总结
笔记·线性代数·矩阵
好家伙VCC4 小时前
数学建模模型 全网最全 数学建模常见算法汇总 含代码分析讲解
大数据·嵌入式硬件·算法·数学建模
茯苓gao4 小时前
STM32G4 速度环开环,电流环闭环 IF模式建模
笔记·stm32·单片机·嵌入式硬件·学习
是誰萆微了承諾4 小时前
【golang学习笔记 gin 】1.2 redis 的使用
笔记·学习·golang