Loading

数论练习

数论

P1891 疯狂 LCM

题意

给定 \(n\),求:

\[\sum_{i = 1}^n \operatorname{lcm}(i, n) \]

思路

先把 \(lcm\) 换成 \(gcd\)

\[\ n\sum_{i = 1}^n \frac{i}{gcd(i,n)} \]

加一个枚举因数的 \(\sum\)

\[\ n\sum_{d|n} \sum_{i = 1}^n \frac{i}{d}[gcd(i,n)=d] \]

\[\ n\sum_{d|n} \sum_{i = 1}^n \frac{i}{d}[gcd(\frac{i}{d},\frac{n}{d})=1] \]

\(i=\frac id\) 替换原本的 \(i\)

\[\ n\sum_{d|n} \sum_{i = 1}^{\frac nd} i[gcd(i,\frac{n}{d})=1] \]

由于 \(d,\frac{n}{d}\) 成对出现,可以用 \(d\) 替换 \(\frac{n}{d}\)

\[\ n\sum_{d|n} \sum_{i = 1}^{d} i[gcd(i,\frac{n}{d})=1] \]

可以发现这个式子与 \(\varphi(d)\) 有一定联系,因为

\[\varphi(d)=\sum_{i = 1}^{d} [gcd(i,\frac{n}{d})=1] \]

多了一个 \(i\)

一个小定理:若 \(gcd(a,b)=1\) \((a<b)\)\(gcd(b-a,b)=1\)

因此原式可以两对两对的统计,可以写成

\[\ n\sum_{d|n} \sum_{i = 1}^{d} \varphi(d)\frac d2 \]

这样就可以计算了

注意 \(d=1\) 的时候特判

#include<bits/stdc++.h>
using namespace std;

inline int read(){
	int x=0,f=1;
	char ch=getchar();
	while(!isdigit(ch)){
		if(ch=='-') f=-1;
		ch=getchar();
	}
	while(isdigit(ch)){
		x=(x<<1)+(x<<3)+(ch^48);
		ch=getchar();
	}
	return x*f;
}

const int N=1e6+5;

#define ll long long

int cnt;
int p[N],phi[N];
bool not_p[N];
ll f[N];

inline void get_phi(){
	phi[1]=1;
    not_p[1]=1;
	for(int i=2;i<N;++i){
		if(!not_p[i]){
			p[++cnt]=i;
			phi[i]=i-1;
		}
		for(int j=1;j<=cnt&&i*p[j]<N;++j){
			not_p[i*p[j]]=1;
			if(i%p[j]==0){
				phi[i*p[j]]=phi[i]*p[j];
				break;
			}
			else
				phi[i*p[j]]=phi[i]*(p[j]-1);
		}
	}
	for(int i=1;i<N;++i)
		for(int j=1;j*i<N;++j)
			f[i*j]+=(i==1?1:1ll*phi[i]*i/2);
}

inline ll solve(int x){
	return f[x]*x;
}

signed main(){
	int T=read();
	get_phi();
	while(T--)
		cout<<solve(read())<<endl;
}

P1128 求正整数

题意

对于任意输入的正整数 \(n\),请求出具有 \(n\) 个不同因子的最小正整数 \(m\)

思路

由于 \(n\leq 5\times 10^4\) ,所以只用考虑前 \(16\) 个质数作为质因子

考虑 \(dfs\)

由于数据太大,将每个数取 \(log\)\(dfs\)

\(dfs(x,y,z)\) 表示搜到的正整数为 \(x\)\(y\)\(x\) 的因数个数,\(z\) 表示搜到了 \(z\) 个质数,然后乱搜

最后再加上一个高精乘单精即可

#include<bits/stdc++.h>
using namespace std;

inline int read(){
	int x=0,f=1;
	char ch=getchar();
	while(!isdigit(ch)){
		if(ch=='-') f=-1;
		ch=getchar();
	}
	while(isdigit(ch)){
		x=(x<<1)+(x<<3)+(ch^48);
		ch=getchar();
	}
	return x*f;
}

int n,ans[100005],res[20],pos[20];
int p[20]={0,2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53};
double mn=1e9,v[20];

inline void dfs(double x,int y,int z){
    if(x>=mn||z>16) return;
    if(y==1){
        mn=x;
        memset(res,0,sizeof(res));
        for(int i=1;i<=z-1;i++)
            res[i]=pos[i];
        return ;
    }
    for(int i=0;(i+1)*(i+1)<=y;++i)
        if(y%(i+1)==0){
            if(i){
                pos[z]=i;
                dfs(x+v[z]*i,y/(i+1),z+1);
            }
            if((i+1)*(i+1)!=y){
                pos[z]=y/(i+1)-1;
                dfs(x+v[z]*(y/(i+1)-1),i+1,z+1);
            }
        }
}

signed main(){
    n=read();
    for(int i=1;i<=16;++i)
        v[i]=log(p[i]);
    dfs(0,n,1);
    int top=1;
    ans[1]=1;
    for(int i=1;i<=16;++i){
        for(;res[i];--res[i]){
            for(int j=1;j<=top;++j) ans[j]*=p[i];
            for(int j=1;j<=top;++j) ans[j+1]+=ans[j]/10,ans[j]%=10;
            if(ans[top+1]) ++top;
            while(ans[top]/10)
                ans[top+1]+=ans[top]/10,ans[top]%=10,++top;
        }
    }
    for(int i=top;i;--i) cout<<ans[i];
}

P2606 排列计数

题意

称一个 \(1 \sim n\) 的排列 \(p_1,p_2, \dots ,p_n\)Magic 的,当且仅当 \(\forall i \in [2,n],p_i > p_{\lfloor i/2 \rfloor}\)
计算 \(1 \sim n\) 的排列中有多少是 Magic 的,对 \(p\) 取模

思路

这道题就是在问:\(1\sim n\)\(n\) 个数构成一棵完全二叉树,求出满足小根堆性质的树总共多少种

考虑 \(dp\)\(f[x]\) 表示当前节点为 \(x\) 的总方案数,节点 \(x\) 的子树大小为 \(sz[x]\)

则有:

\[f[x]=C(sz[x]-1,sz[x\cdot2])\cdot f[x\cdot 2]\cdot f[x\cdot2+1] \]

\((x\cdot 2\text{为左儿子},x\cdot2+1\text{为右儿子})\)

由于要取模,搞个 \(Lucas\)

#include<bits/stdc++.h>
using namespace std;

const int N=2e6+5;

#define int long long

inline int read(){
	int x=0,f=1;
	char ch=getchar();
	while(!isdigit(ch)){
		if(ch=='-') f=-1;
		ch=getchar();
	}
	while(isdigit(ch)){
		x=(x<<1)+(x<<3)+(ch^48);
		ch=getchar();
	}
	return x*f;
}

int n,p;
int f[N],sz[N];
int frac[N];

inline int qpow(int x,int idx){
	if(!idx) return 1;
	int t=qpow(x,idx>>1);
	if(idx&1) return t*t%p*x%p;
	return t*t%p;
}

inline int C(int x,int y){
	if(x<y) return 0;
	return (frac[x]*qpow(frac[y],p-2))%p*qpow(frac[x-y],p-2)%p;
}

inline int Lucas(int x,int y){
	if(!y) return 1;
	return C(x%p,y%p)*Lucas(x/p,y/p)%p;
}

signed main(){
	n=read(),p=read();
	frac[0]=1;
	for(int i=1;i<N;++i) frac[i]=frac[i-1]*i%p;
	for(int i=1;i<=n;++i) sz[i]=1;
	for(int i=n;i>=2;--i) sz[i>>1]+=sz[i];
	for(int i=n+1;i<=n*2+1;++i) f[i]=1;
	for(int i=n;i;--i) f[i]=Lucas(sz[i]-1,sz[i<<1])%p*f[i<<1]%p*f[i<<1|1]%p;
	cout<<f[1]<<endl;
}

P5323 光线

题意

当一束光打到一层玻璃上时,有一定比例的光会穿过这层玻璃,一定比例的光会被反射回去,剩下的光被玻璃吸收。

设对于任意 \(x\),有 \(x \times a_i\%\) 单位的光会穿过它,有 \(x \times b_i\%\) 的会被反射回去。
现在 \(n\) 层玻璃叠在一起,有 \(1\) 单位的光打到第 \(1\) 层玻璃上,那么有多少单位的光能穿过所有 \(n\) 层玻璃呢?

思路

考虑 \(dp\)

\(f[i]\) 表示每 \(1\) 单位光线穿过第 \(i\) 块玻璃的概率是多少,答案显然是 \(\prod f[i]\)

但是光线会反射,还要考虑反射回上一个再反射回来的光线的贡献

\(g[i]\) 表示每 \(1\) 单位光线自下而上经过第 \(i\) 块玻璃然后被向下反射的概率

那么

\(f[i]=a[i]+b[i]\cdot g[i-1]\cdot f[i]\rightarrow f[i]=\frac{a[i]}{1-b[i]\cdot g[i-1]}\)

\(g[i]=b[i]+a[i]\cdot g[i-1]\cdot f[i]\)

\(f[1]=a[1],g[1]=b[1]\)

然后就可以递推了

#include<bits/stdc++.h>
using namespace std;

const int mod=1e9+7;
const int N=5e5+5;

#define int long long

inline int read(){
	int x=0,f=1;
	char ch=getchar();
	while(!isdigit(ch)){
		if(ch=='-') f=-1;
		ch=getchar();
	}
	while(isdigit(ch)){
		x=(x<<1)+(x<<3)+(ch^48);
		ch=getchar();
	}
	return x*f;
}

int n,ans=1;
int a[N],b[N];
int f[N],g[N];

inline int qpow(int x,int idx){
	if(!idx) return 1;
	int t=qpow(x,idx>>1);
	return idx&1?t*t%mod*x%mod:t*t%mod;
}

signed main(){
	n=read();
	int tmp=qpow(100,mod-2);
	for(int i=1;i<=n;++i) a[i]=read()*tmp%mod,b[i]=read()*tmp%mod;
	f[1]=a[1],g[1]=b[1];
	for(int i=2;i<=n;++i){
		tmp=qpow((1-b[i]*g[i-1]%mod+mod)%mod,mod-2);
		f[i]=a[i]*tmp%mod;
		g[i]=(b[i]+(a[i]*g[i-1]%mod*f[i]%mod)%mod)%mod;
	}
	for(int i=1;i<=n;++i) ans=(ans*f[i])%mod;
	printf("%lld",ans);
}
posted @ 2022-07-05 13:48  Into_qwq  阅读(20)  评论(0编辑  收藏  举报