数论基础

T1

第一种做法,考虑(a_0,a_1,b_0,b_1)的因子间的关系。
对于任意一个因子,用(k)来表示该因子的数量。
一定有

  • (k_{a0}>k_{a1})时,(k_x==k_{a1}),不然gcd一定不为(a_1)
  • (k_{a0}==k_{a1})时,只需要(k_x>=k_{a1})
  • (k_{a0}<k_{a1})时,gcd一定取不到(k_{a1}),此时无解

对于(b)的分析也是同理

  • (k_{b0}<k_{b1})时,(k_x==k_{b1}),不然lcm一定不为(b_1)
  • (k_{b0}==k_{b1})时,只需要(k_x<=k_{b1})
  • (k_{b0}>k_{b1})时,lcm一定取不到(k_{b1}),此时无解

最后用分步乘法原理把它乘起来就行
这个解法个人感觉比较难写,但是我把它调出来了,我好棒??

#include<cstdio>
const int N=44800;
bool nprime[N];
int prime[N];
void getp(int n){
	for(int i=2;i<=n;i++){
		if(!nprime[i])
			prime[++prime[0]]=i;
		for(int j=1;j<=prime[0]&&i*prime[j]<=n;j++){
			nprime[i*prime[j]]=1;
			if(i%prime[j]==0)
				break;
		}
	}
}
int a0,a1,b0,b1;
long long res;
int find(int &x,int &t){
	int cnt=0;
	while(x%t==0){
		cnt++;
		x/=t;
	}
	return cnt;
}
bool calc(int t){
	if(t>b1)return 1;
	if(b1%t!=0)return 0;
	int cnt=1;
	int ka1=find(a1,t);
	int ka0=find(a0,t);
	int kb0=find(b0,t);
	int kb1=find(b1,t);
	if(ka1<ka0&&kb0<kb1&&ka1==kb1)cnt=1;
	else if(ka1<ka0&&kb0==kb1&&ka1<=kb1)cnt=1;
	else if(ka1==ka0&&kb0<kb1&&ka1<=kb1)cnt=1;
	else if(ka1==ka0&&kb0==kb1&&ka1<=kb1)cnt=kb1+1-ka1;
	else cnt=0;
	res*=cnt;
	return 0;
}
int main(){
	getp(N-2);
	int T;
	scanf("%d",&T);
	while(T--){
		res=1;
		scanf("%d%d%d%d",&a0,&a1,&b0,&b1);
		for(int i=1;i<=prime[0];i++)
			if(calc(prime[i]))break;
		if(b1!=1)calc(b1);
		printf("%lld
",res);
	}
}

第二种做法,考虑推导一下公式。
已知(gcd(x,a_0)==a_1)(lcm(x,b_0)==b_1)
由gcd的式子,可以推出(gcd(frac{x}{a_1},frac{a_0}{a_1})==1)
注意lcm没有这个性质,但可以转化成gcd,即(xb_0over gcd(x,b_0))(==b_1)
进一步化简得到,(xb_0==gcd(xb_1,b_0b_1))
然后得到(gcd(frac{b_1}{b_0},frac{b_1}{x}))
根据这两个式子,我们只需要枚举到(sqrt{b_1}),然后判断是不是符合条件就行。
借一下(DarthVictor)大佬的代码,这个写法我没写



#include<bits/stdc++.h>
using namespace std;
int gcd(int a,int b) {
    return b==0?a:gcd(b,a%b);
}
int main() {
    int T;
    cin>>T;
    while(T--) {
        int a0,a1,b0,b1;
        cin>>a0>>a1>>b0>>b1;
        int p=a0/a1,q=b1/b0,ans=0;
        for(int x=1;x*x<=b1;x++) 
            if(b1%x==0){
                if(x%a1==0&&gcd(x/a1,p)==1&&gcd(q,b1/x)==1) ans++;
                int y=b1/x;
                if(x==y) continue; 
                if(y%a1==0&&gcd(y/a1,p)==1&&gcd(q,b1/y)==1) ans++;
            }
        cout<<ans<<endl;
    }
    return 0;
}

T2

这是一个裸的欧拉筛。。。。。

#include<cstdio>
const int N=3e6+10;
int prime[N],phi[N];
bool nprime[N];
void init(int n){
	phi[1]=1;
	for(int i=2;i<=n;i++){
		if(!nprime[i]){
			prime[++prime[0]]=i;
			phi[i]=i-1;
		}
		for(int j=1;j<=prime[0]&&i*prime[j]<=n;j++){
			nprime[i*prime[j]]=1;
			if(i%prime[j])
				phi[i*prime[j]]=(prime[j]-1)*phi[i];
			else {
				phi[i*prime[j]]=prime[j]*phi[i];
				break;
			}
		}
	}
}
int main(){
	init(N-5);
	int a,b;
	while(~scanf("%d%d",&a,&b)){
		long long res=0;
		for(int i=a;i<=b;i++)
			res+=phi[i];
		printf("%lld
",res);
	}
}

T3

不妨设(gcd(x,N)==k),那么(gcd(frac{x}{k},frac{N}{k})==1)
所以枚举到(sqrt{N})找到因子,统计答案即(phi(frac{N}{k}))
注意特判(i×i==N)的情况。

#include<cstdio>
int clac(int x){
    int res=1;
    for(int i=2;i*i<=x;i++){
        if(x%i)continue;
        x/=i;res*=i-1;
        while(x%i==0){
            x/=i;res*=i;
        }
    }
    if(x>1)res*=x-1;
    return res;
}
int main(){
    int T;
    scanf("%d",&T);
    while(T--){
        int res=0;
        int N,M;
        scanf("%d%d",&N,&M);
        for(int i=1;i*i<=N;i++){
            if(N%i)continue;
            if(i>=M)res+=clac(N/i);
            if(i*i!=N&&N/i>=M)res+=clac(i);
        }
        printf("%d
",res);
    }
}

T4

举几个例子发现,如果(gcd(x,y)!=1),那么它一定会被前边的某个点遮住,所以要统计的即(gcd(x,y)==1)的情况。
答案即,(sum_{x=0}^{n-1}sum_{y=0}^{n-1}gcd(x,y)==1)
考虑化简这个式子,我们可以把中间的那一斜列拿掉,即(x==y)的情况,这种只对答案贡献1,然后就分为了两个三角形,不难发现这两个三角形对应的答案一样,因为对于每个三角形中的((a,b)),另一个三角形里边一定有一个((b,a))
所以只考虑下边的三角形即可,发现答案化简为(2×sum_{x=1}^{n-1}sum_{y=0}^{x-1}gcd(x,y)==1)
(2×sum_{i=1}^{n-1}phi(i)+1)
注意对于(y=0)的情况,因为这时我们求(gcd(x,y))会直接返回(x)的值,而只有当(x==1)的时候(gcd)才为1,所以可以不用特判。

#include<cstdio>
const int N=4e4+10;
bool nprime[N];
int prime[N],phi[N];
void init(int n){
	phi[1]=1;
	for(int i=2;i<=n;i++){
		if(!nprime[i]){
			prime[++prime[0]]=i;
			phi[i]=i-1;
		}
		for(int j=1;j<=prime[0]&&i*prime[j]<=n;j++){
			nprime[i*prime[j]]=1;
			if(i%prime[j])
				phi[i*prime[j]]=phi[i]*(prime[j]-1);
			else {
				phi[i*prime[j]]=phi[i]*prime[j];
				break;
			}
		}
		phi[i]+=phi[i-1];
	}
}
int main(){
	int n;
	scanf("%d",&n);
	init(n-1);
	printf("%d
",2*phi[n-1]+1);
}

T5

要求出(sum_{i=1}^Ngcd(i,N)),直接求出肯定会T掉。
考虑变形为,(sum{d|N}d×sum_{i=1}^N(gcd(i,N)==d)),理解一下这个式子,最大公约数一定是(N)的约数,所以每有一个(gcd==d)就会对答案产生一个(d)的贡献。
然后(sum_{i=1}^N(gcd(i,N)==d)==sum_{i=1}^N(gcd(frac{i}{d},frac{N}{d})==1)==phi(frac{N}{d}))
仍旧是只枚举到(sqrt{N})

#include<cstdio>
#define ll long long 
ll euler(ll x){
    ll res=1;
    for(int i=2;i*i<=x;i++){
        if(x%i)continue;
        x/=i;res*=i-1;
        while(x%i==0){
            x/=i;res*=i;
        }
    }
    if(x>1)res*=x-1;
    return res;
}
int main(){
    ll n,res=0;
    scanf("%lld",&n);
    for(int i=1;i*i<=n;i++){
        if(n%i)continue;
        res+=i*euler(n/i);
        if(i*i!=n)res+=n/i*euler(i);
    }
    printf("%lld
",res);
}

T6

因为阶乘乘出来十分大,所以需要边乘边取mod,我不认为有人愿意写高精度
因为(M!|N!),与(M!)互质的数一共有(frac{N!}{M!}×phi(M!))个,乘开化简后可以得到(N!×prod_i(frac{p_i-1}{p_i}))
所以只需要在跑欧拉筛的时候顺便维护一下逆元和阶乘即可。
看起来是对的,但是被PinkRabbit大佬hack了。。。。
正解目前也看的不是很懂,先咕了。

#include<cstdio>
#define F(i,a,b) for(int i=(a);i<=(b);++i)
#define F2(i,a,b) for(int i=(a);i<(b);++i)
int T,Mod,n,m;
int primes[664580], pnum=0;
bool isn_prime[10000001];
int pi[664580],inv[10000001];
int in[664580],fct[10000001];
int pos[10000001];
void init(){
    isn_prime[0]=isn_prime[1]=1;
    F(i,2,10000000){
        if(!isn_prime[i]) primes[++pnum]=i;
        for(int j=1;j<=pnum&&primes[j]*i<=10000000;++j){
            isn_prime[primes[j]*i]=1;
            if(i%primes[j]==0) break;
        }
    }
    inv[1]=1; for(int i=2;i<Mod&&i<=10000000;++i)
        inv[i]=1ll*(Mod-Mod/i)*inv[Mod%i]%Mod;
    pi[0]=1; F(i,1,pnum) pi[i]=1ll*pi[i-1]*(primes[i]-1)%Mod;
    in[0]=1; F(i,1,pnum) if(primes[i]!=Mod) in[i]=1ll*in[i-1]*inv[primes[i]%Mod]%Mod; else in[i]=in[i-1];
    fct[0]=1; F(i,1,10000000) if(i!=Mod) fct[i]=1ll*fct[i-1]*i%Mod; else fct[i]=fct[i-1];
    F(i,2,10000000) if(isn_prime[i]) pos[i]=pos[i-1]; else pos[i]=pos[i-1]+1; 
}
int main(){
    scanf("%d%d",&T,&Mod);
    init();
    while(T--){
        scanf("%d%d",&n,&m);
        if(n>=Mod&&m<Mod) puts("0");
        else printf("%d
",1ll*fct[n]*pi[pos[m]]%Mod*in[pos[m]]%Mod);
    }
    return 0;
}

T7
exgcd裸题,要求(axequiv1pmod{b})
可以把它变化成为(ax-by=1)
因为只求(x),所以变换(y)的系数没关系,得到(ax+by==1)
因为一定有解,所以,(gcd(a,b)==1),所以(bx'+a%by'==1)
然后得到(ax+by==bx'+(a-lfloorfrac{a}{b} floor)y')
又因为求整数解,所以让对应的系数相等即可。
得到(x=(a-lfloorfrac{a}{b} floor)y'),(y=x')

#include<cstdio>
#define ll long long
void exgcd(ll a,ll b,ll &x,ll &y){
	if(b==0){
		x=1;
		y=0;
		return ;
	}
	exgcd(b,a%b,y,x);
	y-=a/b*x;
}
int main(){
	ll a,b,x,y;
	scanf("%lld%lld",&a,&b);
	exgcd(a,b,x,y);
	printf("%lld
",(x%b+b)%b);
}

T8

还是个exgcd。
假设跳了(k)次后相遇,那么(x+kmequiv y+nkpmod{L})
化简可以得到(k(n-m)+pL==x-y),其中p为常数。
然后只要解出(k)即可。
但这里让求最小整数解,所以考虑转化。
令特解为(x_0,y_0),那么有
(ax_0+by_0==ax+by)
(a(x_0-x)==b(y-y_0))
(frac{a}{gcd(a,b)}(x_0-x)==frac{b}{gcd(a,b)}(y-y_0))
这时由于系数是互质的,
(x_0-x==frac{b}{gcd(a,b)}),
所以(x==x_0-frac{b}{gcd(a,b)})

#include<cstdio>
#define ll long long
ll exgcd(ll a,ll b,ll &x,ll &y){
	if(b==0){
		x=1;
		y=0;
		return a;
	}
	ll g=exgcd(b,a%b,y,x);
	y-=a/b*x;
	return g;
}
int main(){
	ll x,y,n,m,L;
	scanf("%lld%lld%lld%lld%lld",&x,&y,&m,&n,&L);
	ll a=n-m,c=x-y,b=L;
	if(a<0)a=-a,c=-c;
	ll g=exgcd(a,b,x,y);
	if(m==n||c%g)
		printf("Impossible
");
	else
		printf("%lld
",(c/g*x%(b/g)+b/g)%(b/g));
}

T9

思路基本和上一个差不多,主要考虑什么时候得到体积最小
(V==-ax+by),如果它要是有解,那么(gcd(a,b)|V),所以(V_min==gcd(a,b))

#include<cstdio>
#define lqs long long
lqs exgcd(lqs a,lqs b,lqs &x,lqs &y){
	if(b==0){
		x=1;
		y=0;
		return a;
	}
	lqs g=exgcd(b,a%b,y,x);
	y-=a/b*x;
	return g;
}
int main(){
	lqs a,b,x,y;
	scanf("%lld%lld",&a,&b);
	lqs g=exgcd(a,b,x,y);
	printf("%lld
",g);
	x*=-1;a*=-1;
	while(x<0||y<0){
		x+=(x<0)?b/g:0;
		y-=(x>=0)?a/g:0;
	}
	printf("%lld %lld
",x,y);
}

T10

显然是递推求逆元。
(Mod=ki+r),则有(ki+requiv 0pmod{Mod})
同时乘上(i^{-1}r^{-1})得到(kr^{-1}+i_{-1}equiv 0pmod{Mod})
所以(i^{-1}equiv-kr^{-1}pmod{Mod})
(i^{-1}equiv-lfloorfrac{Mod}{i} floor(p%i)^{-1})

T11

一种是去找循环节,但是时间复杂度显然很SPFA。
易得当(x<=frac{n}{2})时,(x=2x)
(x>frac{n}{2})时,(x=2x-n-1)
那么有没有什么好的办法来用一个统一的式子表示呢?
显然(2xequiv 2x-n-1 pmod{n+1})
所以只要%上一个(n+1)即可,最后得到的答案也不会受到影响因为起始位置一定小于(n+1)
所以解方程(2^Mx==Lpmod{n+1})即可。
解得方法有很多,随便挑一种就行。
还有洛谷上的数据经过魔改所以过不掉

#include<iostream>
using namespace std;
typedef long long ll;
ll pow(ll a,ll b,ll c){
	ll ans=1;
	while(b){
		if(b&1)ans=ans*a%c;
		a=a*a%c;
		b>>=1;
	}
	return ans;
}
void exgcd(ll a,ll b,ll &x,ll &y){
	if(b==0){
		x=1;
		y=0;
		return;
	}
	exgcd(b,a%b,y,x);
	y-=a/b*x;
}
int main(){
	ll n,m,l,x,y;
	cin>>n>>m>>l;
	exgcd(pow(2,m,n+1),n+1,x,y);
	cout<<(l*x%(n+1)+n+1)%(n+1);	
}

尾声

赶来赶去最后终于是在十一点之前搞完了,正如题目说的那样,数论基础,数学的学习才刚刚开始,后边还有更大的挑战。
由于写的匆忙,所以有些地方可能会写错,只是笔误,欢迎指正。
代码可以复制粘贴但有些题目代码加有防伪标记,咳咳咳咳

原文地址:https://www.cnblogs.com/anyixing-fly/p/12980162.html