数论基础

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\n",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_0\over 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\n",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\n",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\n",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\n",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\n",1ll*fct[n]*pi[pos[m]]%Mod*in[pos[m]]%Mod);
        }
        return 0;
}

T7
exgcd裸题,要求\(ax\equiv1\pmod{b}\)
可以把它变化成为\(ax-by=1\),
因为只求\(x\),所以变换\(y\)的系数没关系,得到\(ax+by==1\)
因为一定有解,所以,\(gcd(a,b)==1\),所以\(bx'+a%by'==1\)
然后得到\(ax+by==bx'+(a-\lfloor\frac{a}{b}\rfloor)y'\)
又因为求整数解,所以让对应的系数相等即可。
得到\(x=(a-\lfloor\frac{a}{b}\rfloor)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\n",(x%b+b)%b);
}

T8

还是个exgcd。
假设跳了\(k\)次后相遇,那么\(x+km\equiv y+nk\pmod{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\n");
	else
		printf("%lld\n",(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\n",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\n",x,y);
}

T10

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

T11

一种是去找循环节,但是时间复杂度显然很SPFA。
易得当\(x<=\frac{n}{2}\)时,\(x=2x\)。
当\(x>\frac{n}{2}\)时,\(x=2x-n-1\)
那么有没有什么好的办法来用一个统一的式子表示呢?
显然\(2x\equiv 2x-n-1 \pmod{n+1}\)
所以只要%上一个\(n+1\)即可,最后得到的答案也不会受到影响因为起始位置一定小于\(n+1\)
所以解方程\(2^Mx==L\pmod{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);	
}

尾声

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

上一篇:[数论]拓展欧几里得算法


下一篇:12.扩展欧几里得算法