(停更)[2019 CSP冲刺 数论]数论提高题选做

本文深入探讨了数论算法在编程竞赛中的应用,包括质因数分解、扩展欧拉定理、快速幂计算等核心技巧,并通过具体题目解析,如CF1034A、CF906D、CF17D等,展示了如何高效解决复杂数学问题。

本博客主要记录了自己的刷题内容,因此每道题不会写太多,请谅解。

Part 1 数论提高题选做

CF1034A Enlarge GCD

给你n个数,去掉尽量少的数使得剩下数的gcd比原来的大。无解输出-1

若使gcd变大,我们只需要找到一个不是所有数都有的一个质因子,然后将没有这个质因子的这些数删去即可,因此我们直接统计每个质因子出现次数,取n−最小值n-最小值n即可。
时间复杂度:O(namax)O(n \sqrt{a_{max}})O(namax)

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<vector>
using namespace std;
#define MAXP 15000000
#define MAXN 300000
#define LL long long
bool isp[MAXP+5];
int a[MAXN+5];
int cnt[MAXP+5];
int prime[MAXN*10+5];
int Pnum=0;
int n,ans;
LL gcd(LL m,LL n)
{
    while(m>0)
    {LL c=n%m;n=m,m=c;}
    return n;
}
void Prime(int Sl)
{
    isp[1]=1;
    for(int i=2;i<=Sl;i++)
	{
		if(!isp[i])prime[Pnum++]=i;
		for(int j=0;j<Pnum&&prime[j]*i<=Sl;j++)
		{
			isp[i*prime[j]]=1;
			if(i%prime[j]==0)break;
		}
	}
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    scanf("%d",&a[i]);
    Prime(MAXP);
    int G=a[1];
    for(int i=2;i<=n;i++)G=gcd(G,a[i]);
    for(int i=1;i<=n;i++)
    {
        a[i]/=G;
        for(int j=0;a[i]>=prime[j]*prime[j];j++)
        {
            if(a[i]%prime[j]==0)cnt[prime[j]]++;
            while(a[i]%prime[j]==0)a[i]/=prime[j];
        }
        if(a[i]!=0)cnt[a[i]]++;
    }
    ans=n;
    for(int j=0;j<Pnum;j++)
    ans=min(ans,n-cnt[prime[j]]);
    printf("%d",ans==n? -1:ans);
}

CF906D Power Tower

给定一个数列w1,w2,...,wnw_1,w_2,...,w_nw1,w2,...,wn和模数ppp,每次询问一个区间[l,r][l,r][l,r],求wlwl+1wl+2...wrmod  pw_l^{w_{l+1}^{w_{l+2}^{{...}^{w_r}}}} \mod pwlwl+1wl+2...wrmodp的值

一道很玄学的题目。
首先ppp不是质数,因此我们无法使用欧拉定理。
然而有一个神奇的扩展欧拉定理
当b≥ϕ(p)时,有ab≡abmod  ϕ(p)+ϕ(p)当 b≥\phi(p) 时,有a^b≡a^{b \mod \phi(p)+\phi(p)}bϕ(p),ababmodϕ(p)+ϕ(p)
因此我们在取模时直接加上一个ϕ(p)\phi(p)ϕ(p)即可。
然后我们直接暴力递归计算。
然后它就过了…
这是因为ϕ(ϕ(ϕ(...ϕ(p))))\phi(\phi(\phi(...\phi(p))))ϕ(ϕ(ϕ(...ϕ(p))))很快就会变为111,大概时O(log⁡p)O(\log p)O(logp)级别,这时候退出即可。

#include<iostream>
#include<cstdio>
#include<map>
#include<algorithm>
#include<cmath>
using namespace std;
#define MAXN 100000
#define LL long long
int read(){
	int x=0,F=1;char c=getchar();
	while(c<'0'||c>'9'){if(c=='-')F=-1;c=getchar();}
	while(c>='0'&&c<='9'){x=(x<<3)+(x<<1)+c-'0';c=getchar();}
	return x*F;
}
int n,MOD,a[MAXN+1],Q;
map <int,int> phi;
LL pMOD(LL x,LL p){return x<p?x:(p+x%p);}
int Phi(int x){
	int res=x,ps=x;
	if(phi.count(x))return phi[x];
	for(int i=2;i<=sqrt(x);i++)
	if(x%i==0){
		res=res/i*(i-1);
		while(x%i==0)x/=i;
	}
	if(x>1)res=res/x*(x-1);
	return phi[ps]=res;
}
int Qpow(int a,int b,int p){
	int res=1;
	while(b){
		if(b&1)res=pMOD(1LL*res*a,p);
		a=pMOD(1LL*a*a,p);
		b>>=1;
	}return res;
}
int get_ans(int l,int r,int p){
	if(l==r||p==1)return pMOD(a[l],p);
	return Qpow(a[l],get_ans(l+1,r,Phi(p)),p);
}
int main()
{
	n=read(),MOD=read();
	for(int i=1;i<=n;i++)a[i]=read();
	Q=read();
	while(Q--){
		int l=read(),r=read();
		printf("%d\n",get_ans(l,r,MOD)%MOD);
	}
}

CF17D Notepad

(b−1)bn−1 mod c(b-1)b^{n-1} \bmod c(b1)bn1modc
b≤10106,n≤10106,c≤109b \leq 10^{10^6},n \leq 10^{10^6},c \leq 10^9b10106,n10106,c109

运用上一题的扩展欧拉定理直接快速幂计算即可。

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
#define MAXN 1000000
#define LL long long
char s1[MAXN+5],s2[MAXN+5];
int MOD;
LL A,B,b[MAXN+5],F;
int phi(int x){
	int ps=x,res=x;
	for(int i=2;i*i<=ps;i++)
	if(x%i==0){
		res=res/i*(i-1);
		while(x%i==0)x/=i;
	}
	if(x!=1)res=res/x*(x-1);
	return res;
}
int fst_pow(int a,int b,int p){
	int res=1;
	while(b){
		if(b&1)res=(1LL*res*a)%p;
		a=(1LL*a*a)%p;
		b>>=1;
	}return res;
}
int main()
{
	scanf("%s%s%d",s1+1,s2+1,&MOD);
	int l1=strlen(s1+1),l2=strlen(s2+1),P=phi(MOD);
	for(int i=1;i<=l1;i++)A=(A*10+s1[i]-'0')%MOD;
	for(int i=1;i<=l2;i++)b[i]=s2[i]-'0';
	b[l2]--;
	for(int i=l2;i>=1;i--)
	if(b[i]<0)b[i-1]--,b[i]+=10;
	else break;
	for(int i=1;i<=l2;i++){
		B=B*10+b[i];
		if(B>=P)F=1,B%=P;
	}
	int Ans=1LL*(A-1+MOD)%MOD*fst_pow(A,F?B+P:B,MOD)%MOD;
	printf("%d",Ans?Ans:MOD);
}

CF1033D Divisors

给你nnn个数a1 ana_1~a_na1 an,每个数都有3−53-535个因数,现在要求A=∏aiA=\prod a_iA=ai的因数个数,保证ai≤2×1018a_i≤2 \times 10^{18}ai2×1018

其实我并不相信这题难度只有2000…
根据定理得∏(cnti+1)\prod (cnt_i+1)(cnti+1)
首先先分析满足条件的aaa组成只有:

p1p2p_1p_2p1p2p12p_1^2p12p13p_1^3p13p14p_1^4p14

对于后面三个情况我们直接用C++ pow函数开方即可(注意精度)。
但是对于第一个情况就比较麻烦了…
有一种比较巧妙的做法,考虑答案只与相同的质因子有关,于是我们找其他与其他数的gcd,如果不是本身和1,就能得到p1,p2p_1,p_2p1,p2。如果没有公共因子,直接将答案乘上(cnt+1)2(cnt+1)^2(cnt+1)2即可。
复杂度:O(n2log⁡max⁡ai)O(n^2 \log \max{a_i})O(n2logmaxai)
这题的交互是用来搞笑的吗…

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<vector>
#include<map>
#include<set>
using namespace std;
#define MAXN 500
#define eps 10e-6
#define MOD 998244353
#define LL long long
int n,vis[MAXN+5];
LL a[MAXN+5],ans;
vector<LL> g;
map<LL,int> D; 
set<LL> s;
int main()
{
	scanf("%d",&n);ans=1;
	for(int i=1;i<=n;i++){
		scanf("%lld",&a[i]);
		LL p2=pow((double)a[i],0.5)+eps,p3=pow((double)a[i],1/3.0)+eps,p4=pow((double)a[i],0.25)+eps;
		if(p4*p4*p4*p4==a[i]){D[p4]+=4;s.insert(p4);continue;}
		if(p3*p3*p3==a[i]){D[p3]+=3;s.insert(p3);continue;}
		if(p2*p2==a[i]){D[p2]+=2;s.insert(p2);continue;}
		g.push_back(a[i]);
	}
	sort(g.begin(),g.end());
	for(int i=0;i<g.size();i++){
		int cnt=0;LL res=-1;
		if(vis[i])continue;
		for(int j=0;j<g.size();j++){
			if(g[i]==g[j]){vis[j]=1;cnt++;continue;}
			LL G=__gcd(g[i],g[j]);
			if(G!=1&&G!=g[i])res=G;
		}
		for(auto x:s)
		if(g[i]%x==0)res=x;
		if(res==-1){ans=(ans*(cnt+1)*(cnt+1))%MOD;}
		else D[res]+=cnt,D[g[i]/res]+=cnt;
	}
	for(auto x:D)
	ans=(ans*(x.second+1))%MOD;
	printf("%lld",ans);
}

CF582A GCD Table

给定一个N×MN\times MN×M的数表,其中第iii行第jjj个数为gcd⁡(i,j)\gcd(i,j)gcd(i,j),再给给定一个长度K≤104K\leq 10^4K104的数列aaa,判断是否在数表的某一行出现过。 N,M≤1012N,M\leq 10^{12}N,M1012

简单题,我们每次挑当前最大的数,然后将之前选中的每个数取gcd⁡\gcdgcd的数都删去两个即可。

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<map> 
using namespace std;
#define MAXN 250000
int n,tp;
int a[MAXN+5],cnt[MAXN+5],ans[MAXN+1];
map<int,int> id; 
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n*n;i++)
	scanf("%d",&a[i]);
	for(int i=1;i<=n*n;i++)
	id[a[i]]++;
	sort(a+1,a+n*n+1);
	int pn=unique(a+1,a+n*n+1)-a-1;
	for(int i=1;i<=pn;i++)
	cnt[i]=id[a[i]],id[a[i]]=i;
	for(int i=pn;i>=1;i--)
		while(cnt[i]){
			ans[++tp]=a[i];
			printf("%d ",ans[tp]);
			cnt[i]--;
			for(int j=1;j<tp;j++)
			cnt[id[__gcd(ans[tp],ans[j])]]-=2;
		}
}

CF1027G X-mouse in the Campus

Part 2 专题归纳

数论基础定理

中国剩余定理/扩展中国剩余定理(CRT/EXCRT)

Polard Rho算法

离散对数/高次剩余

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值