一些數論函式題
luogu2257 YY的GCD
答案為:
\(\sum\limits_{p\in prime}\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{m}[gcd(i,j)==p]\)
\(=\sum\limits_{p\in prime}\sum\limits_{i=1}^{\lfloor\frac{n}{p}\rfloor}\sum\limits_{j=1}^{{\lfloor\frac{m}{p}}\rfloor}[gcd(i,j)==1]\)
\(=\sum\limits_{p\in prime}\sum\limits_{i=1}^{\lfloor\frac{n}{p}\rfloor}\sum\limits_{j=1}^{{\lfloor\frac{m}{p}}\rfloor}\sum\limits_{t \mid i\land t\mid j}\mu(t)\)
\(=\sum\limits_{p\in prime}\sum\limits_{t=1}^{min(n,m)}\mu(t)\sum\limits_{i=1}^{\lfloor\frac{n}{pt}\rfloor}\sum\limits_{j=1}^{{\lfloor\frac{m}{pt}}\rfloor}1\)
\(=\sum\limits_{p\in prime}\sum\limits_{t=1}^{min(n,m)}\mu(t)\lfloor\frac{n}{pt}\rfloor\lfloor\frac{m}{pt}\rfloor\)
令\(T=pt\)。
原式\(=\sum\limits_{p\in prime}\sum\limits_{t=1}^{min(n,m)}\mu(t)\lfloor\frac{n}{T}\rfloor\lfloor\frac{m}{T}\rfloor\)
\(=\sum\limits_{T=1}^{min(n,m)}\lfloor\frac{n}{T}\rfloor\lfloor\frac{m}{T}\rfloor\sum\limits_{p\in prime \land p\mid T}\mu(\frac{T}{p})\)
前面的\(\sum\limits_{T=1}^{min(n,m)}\lfloor\frac{n}{T}\rfloor\lfloor\frac{m}{T}\rfloor\)就是一個整除分塊。這就需要我們求出\(\sum\limits_{p\in prime \land p\mid T}\mu(\frac{T}{p})\)的前綴合,我們列舉p然後遍歷值域就是調和級數複雜度的預處理。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;
int mu[10000002],prim[1000000],vis[10000002],g[10000002],sum[10000002],cnt,n[10100],m[10100],t;
long long ans;
inline void read(int &x)
{
x=0;
static int p;p=1;
static char c;c=getchar();
while(!isdigit(c)){if(c=='-')p=-1;c=getchar();}
while(isdigit(c)) {x=(x<<1)+(x<<3)+(c-48);c=getchar();}
x*=p;
}
void getmu(int n){
mu[1]=1;
for(int i=2;i<=n;i++){
if(!vis[i]){
prim[++cnt]=i;
mu[i]=-1;
}
for(int j=1;i*prim[j]<=n;j++){
vis[i*prim[j]]=1;
if(i%prim[j]==0)break;
else mu[i*prim[j]]=-mu[i];
}
}
for(int i=1;i<=cnt;i++)
for(int j=1;j*prim[i]<=n;j++)
g[j*prim[i]]+=mu[j];
for(int i=1;i<=n;i++)
sum[i]=sum[i-1]+g[i];
}
int main(){
read(t);
int tot=0;
for(int i=1;i<=t;i++){
read(n[i]);read(m[i]);
tot=max(tot,max(n[i],m[i]));
}
getmu(tot);
for(int i=1;i<=t;i++){
ans=0;
if(n[i]>m[i])swap(n[i],m[i]);
for(int l=1,r;l<=n[i];l=r+1){
r=min(n[i]/(n[i]/l),m[i]/(m[i]/l));
ans+=(long long)(n[i]/l)*(m[i]/l)*(sum[r]-sum[l-1]);
}
printf("%lld\n",ans);
}
return 0;
}
[CQOI2015]選數
顯然是一道莫比烏斯反演。
設f[i]為選出n個數gcd為i的方案數。
設F[i]為選出n個數gcd為i的倍數的方案數。
把L,H都除去k,答案就是f[1](注意一點除的時候當L%k!=0是L除完k之後還要加1)。
然後有\(F[i]=\sum\limits_{i \mid d }f[d]\)
\(f[i]=\sum\limits_{i \mid d} \mu(\frac{d}{i})F[d]\)
應為我們要求的是f[1],
\(f[1]=\sum\limits_d \mu(d)F[d]\)
F怎麼求就是可以選的數的n次方:\((\lfloor\frac{H}{k}\rfloor-\lfloor\frac{L-1}{k}\rfloor)^n\)
\(\mu\)字首和用杜教篩就行了。
#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<cstdio>
#include<map>
using namespace std;
#define int long long
map<int,bool>vis;
map<int,int> hsh;
const int N=2001000;
const int p=1e9+7;
int mu[N],book[N],prime[N],cnt,n,k,L,H;
int read(){
int sum=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){sum=sum*10+ch-'0';ch=getchar();}
return sum*f;
}
void pre_work(){
mu[1]=1;
for(int i=2;i<=1000000;i++){
if(book[i]==0){
prime[++cnt]=i;
mu[i]=-1;
}
for(int j=1;j<=cnt&&prime[j]*i<=1000000;j++){
book[i*prime[j]]=1;
if(i%prime[j]==0)break;
mu[i*prime[j]]=-mu[i];
}
}
for(int i=1;i<=1000000;i++)mu[i]+=mu[i-1];
}
int sum(int x){
if(x<=1000000)return mu[x];
if(vis[x]==1)return hsh[x];
int tmp=1;
for(int l=2,r;l<=x;l=r+1){
r=(x/(x/l));
tmp=tmp-(r-l+1ll)*sum(x/l)%p;
tmp=(tmp%p+p)%p;
}
vis[x]=1;hsh[x]=tmp;
return tmp;
}
int ksm(int x,int b){
int tmp=1;
while(b){
if(b&1)tmp=tmp*x%p;
b>>=1;
x=x*x%p;
}
return tmp;
}
int work(){
int tmp=0;
for(int l=1,r;l<=H;l=r+1){
if(l>L-1ll)r=H/(H/l);
else r=min(H/(H/l),(L-1ll)/((L-1ll)/l));
tmp=tmp+(sum(r)-sum(l-1ll))*ksm((H/l-(L-1ll)/l),n)%p;
tmp=(tmp%p+p)%p;
}
return tmp;
}
signed main(){
pre_work();
n=read();k=read();L=read();H=read();
if(L%k)L=L/k+1;
else L=L/k;
H=H/k;
printf("%lld",work());
return 0;
}
BZOJ 4176 Lucas的數論
\((n \leq 1e9)\)f代表約數個數。
\(f(ij)=\sum\limits_{x \mid i }\sum\limits_{ y\mid j}[gcd(x,y)==1]\)
我有一個精巧的證明可惜這裡地方太小,寫不下。
(我才知道這是費馬說的,思維風暴證明費馬大定理?)
我才沒有什麼精巧的證明,我會暴力。
因為約束個數函式是極性函式,把每一個質因子分開考慮。
設ij中有\(p^k\),i中有\(p^a\),j中有\(p^b\),其中a+b=k。
然後我們驚奇的發現\(p^k\)的約數個數為k+1=a+b+1。然後\(p^a\),和\(p^b\)互質的情況有a+b+1。
然後就可以嘿嘿嘿了。
原式等於:\(\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{n}\sum\limits_{x \mid i }\sum\limits_{ y\mid j}[gcd(x,y)==1]\)
\(=\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{n}\sum\limits_{x \mid i }\sum\limits_{y\mid j}\sum\limits_{t\mid x\land t\mid y}\mu(t)\)
\(=\sum\limits_{t=1}^{n}\mu(t)\sum\limits_{x=1}^{\lfloor\frac{n}{t}\rfloor}\lfloor\frac{n}{tx}\rfloor\sum\limits_{y=1}^{\lfloor\frac{n}{t}\rfloor}\lfloor\frac{n}{ty}\rfloor\)
\(=\sum\limits_{t=1}^{n}\mu(t) (\sum\limits_{x=1}^{ \lfloor \frac{n}{t} \rfloor} \lfloor\frac{n}{tx}\rfloor)^2\)
然後上整除分塊。
複雜度。。聽說是\(O(\sqrt{n}log\sqrt{n})\)
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<map>
using namespace std;
#define int long long
const int p=1e9+7;
const int N=1010000;
map<int,int>hsh;
map<int,bool>vis;
int mu[N],book[N],prime[N],cnt,n;
void pre_work(){
mu[1]=1;
for(int i=2;i<=1000000;i++){
if(book[i]==0){
prime[++cnt]=i;
mu[i]=-1;
}
for(int j=1;j<=cnt&&prime[j]*i<=1000000;j++){
book[i*prime[j]]=1;
if(i%prime[j]==0)break;
mu[i*prime[j]]=-mu[i];
}
}
for(int i=1;i<=1000000;i++)mu[i]+=mu[i-1];
}
int work(int x){
int tmp=0;
for(int l=1,r;l<=x;l=r+1){
r=x/(x/l);
tmp=(tmp+(r-l+1)*(x/l)%p)%p;
}
return tmp;
}
int sum(int x){
if(x<=1000000)return mu[x];
if(vis[x])return hsh[x];
int tmp=1;
for(int l=2,r;l<=x;l=r+1){
r=x/(x/l);
tmp=tmp-sum(x/l)*(r-l+1);
tmp=(tmp%p+p)%p;
}
vis[x]=1;hsh[x]=tmp;
return tmp;
}
int calc(int x){
int tmp=0;
for(int l=1,r;l<=x;l=r+1){
r=x/(x/l);
int w=work(x/l);
tmp=tmp+(sum(r)-sum(l-1))*w%p*w%p;
tmp=(tmp%p+p)%p;
}
return tmp;
}
int read(){
int sum=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){sum=sum*10+ch-'0';ch=getchar();}
return sum*f;
}
signed main(){
n=read();
pre_work();
printf("%lld",calc(n));
return 0;
}
luogu P3768 簡單的數學題
\(\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{m}ijgcd(i,j)\)
gcd不僅可以化成\(\mu\)
原式等於
\(\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{n}ij\sum\limits_{t \mid i \land t \mid j} \varphi(t)\)
為什麼?
因為\(\varphi*1\)=id
然後接著化簡。
\(\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{n}ij\sum\limits_{t \mid i \land t \mid j} \varphi(t)\)
\(=\sum\limits_{t=1}^{n}\varphi(t)*t^2\sum\limits_{i=1}^{\lfloor\frac{n}{t}\rfloor}\sum\limits_{j=1}^{\lfloor\frac{n}{t}\rfloor}ij\)
\(=\sum\limits_{t=1}^{n}\varphi(t)*t^2(\sum\limits_{i=1}^{\lfloor\frac{n}{t}\rfloor}i)^2\)
\(=\sum\limits_{t=1}^{n}\varphi(t)*t^2\sum\limits_{i=1}^{\lfloor\frac{n}{t}\rfloor}i^3\)
然後就可以做了。
最後一步怎麼化簡?暴力展開。。。
然後上杜教篩就行了。
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<map>
using namespace std;
#define int long long
const int p=1e9+7;
const int N=1010000;
map<int,int>hsh;
map<int,bool>vis;
int mu[N],book[N],prime[N],cnt,n;
void pre_work(){
mu[1]=1;
for(int i=2;i<=1000000;i++){
if(book[i]==0){
prime[++cnt]=i;
mu[i]=-1;
}
for(int j=1;j<=cnt&&prime[j]*i<=1000000;j++){
book[i*prime[j]]=1;
if(i%prime[j]==0)break;
mu[i*prime[j]]=-mu[i];
}
}
for(int i=1;i<=1000000;i++)mu[i]+=mu[i-1];
}
int work(int x){
int tmp=0;
for(int l=1,r;l<=x;l=r+1){
r=x/(x/l);
tmp=(tmp+(r-l+1)*(x/l)%p)%p;
}
return tmp;
}
int sum(int x){
if(x<=1000000)return mu[x];
if(vis[x])return hsh[x];
int tmp=1;
for(int l=2,r;l<=x;l=r+1){
r=x/(x/l);
tmp=tmp-sum(x/l)*(r-l+1);
tmp=(tmp%p+p)%p;
}
vis[x]=1;hsh[x]=tmp;
return tmp;
}
int calc(int x){
int tmp=0;
for(int l=1,r;l<=x;l=r+1){
r=x/(x/l);
int w=work(x/l);
tmp=tmp+(sum(r)-sum(l-1))*w%p*w%p;
tmp=(tmp%p+p)%p;
}
return tmp;
}
int read(){
int sum=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){sum=sum*10+ch-'0';ch=getchar();}
return sum*f;
}
signed main(){
n=read();
pre_work();
printf("%lld",calc(n));
return 0;
}
[SDOI2017]數字表格
\(\Pi_{i=1}^{n}\Pi_{j=1}^{m}f[gcd(i,j)]\)
\(=\Pi_{d=1} f[d]^{\sum\limits_{i=1}^{\lfloor\frac{n}{d}\rfloor}\sum\limits_{j=1}^{\lfloor\frac{n}{d}\rfloor}[gcd(i,j)==1]}\)
\(=\Pi_{d=1} f[d]^{\sum\limits_{i=1}^{\lfloor\frac{n}{d}\rfloor}\sum\limits_{j=1}^{\lfloor\frac{n}{d}\rfloor}\sum\limits_{t \mid i\land t\mid j}\mu(t)}\)
\(=\Pi_{d=1} f[d]^{\sum\limits_{i=1}^{min(n,m)}\mu(t)\lfloor\frac{n}{dt}\rfloor\lfloor\frac{m}{dt}\rfloor}\)
令T=dt
\(=\Pi_{T=1}^{min(m,n)}\Pi_{d\mid T} f[d]^{\mu(\frac{T}{d})\lfloor\frac{n}{T}\rfloor\lfloor\frac{m}{T}\rfloor}\)
然後我們把\(\lfloor\frac{n}{T}\rfloor\lfloor\frac{m}{T}\rfloor\)分塊。\(\Pi_{d\mid T} f[d]^{\mu(\frac{T}{d})}\)預處理。因為調和級數複雜度可以接受。
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<algorithm>
using namespace std;
#define int long long
const int mod=1e9+7;
const int N=1010100;
int mu[N],book[N],prime[N],cnt,f[N],T,n,m,F[N],inv[N];
int read(){
int sum=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){sum=sum*10+ch-'0';ch=getchar();}
return sum*f;
}
int ksm(int x,int b){
int tmp=1;
while(b){
if(b&1)tmp=tmp*x%mod;
b>>=1;
x=x*x%mod;
}
return tmp;
}
void pre_work(){
mu[1]=1;
for(int i=2;i<=1000000;i++){
if(book[i]==0){
mu[i]=-1;
prime[++cnt]=i;
}
for(int j=1;j<=cnt&&i*prime[j]<=1000000;j++){
book[i*prime[j]]=1;
if(i%prime[j]==0)break;
mu[i*prime[j]]=-mu[i];
}
}
f[0]=0;f[1]=1;
for(int i=2;i<=1000000;i++)
f[i]=(f[i-1]+f[i-2])%mod;
for(int i=0;i<=1000000;i++)F[i]=1;
for(int i=1;i<=1000000;i++)
for(int j=1;j*i<=1000000;j++){
int tmp;
if(mu[j]==0)tmp=1;
else if(mu[j]==1)tmp=f[i];
else tmp=ksm(f[i],mod-2);
F[i*j]=(F[i*j]*tmp)%mod;
}
for(int i=2;i<=1000000;i++)F[i]=(F[i-1]*F[i])%mod;
for(int i=1;i<=1000000;i++)inv[i]=ksm(F[i],mod-2);
inv[0]=1;
}
int work(int n,int m){
if(n>m)swap(n,m);
int tmp=1;
for(int l=1,r;l<=n;l=r+1){
r=min(m/(m/l),n/(n/l));
tmp=(tmp*ksm(F[r]*inv[l-1]%mod,(n/l)*(m/l)))%mod;
}
return tmp;
}
signed main(){
T=read();
pre_work();
while(T--){
n=read(),m=read();
printf("%lld\n",work(n,m));
}
return 0;
}