NOIP2024集训Day60

非常好数论复习。

CF986F Oppa Funcan Style Remastered

给定 \(n\)\(k\),问是否能将 \(n\) 分为若干个 \(k\) 的因数(\(1\) 除外)之和,每个因数都可以被选多次。

\(n\leq 10^{18}\)\(k\leq 10^{15}\),最多 \(50\) 种不同的 \(k\)

一共\(t\)组询问,\(t\leq 10^4\)


首先,注意到我们可以把选的数的范围缩小到 \(k\) 的所有质因子,这是不超过 20 个的。

那么现在问题就变成了判断能否用若干个数加出 \(n\),容易发现这是同余最短路的模板。

但是还有一个问题就是同余最短路的复杂度是与最小的数相关的,如果 \(k\) 是一个大质数的话复杂度是无法接受的。

观察到如果当 \(k\) 有超过 3 个不同的质因子时,最小的质因子是小于 \(10^5\) 的,所以我们考虑对于质因子数超过 3 的 \(k\) 跑同余最短路,而对质因子数为 0,1,2 的 \(k\) 单独处理:

  • \(k\) 的质因子数为 0,即 \(k=1\) 时,无解。
  • \(k\) 的质因子数为 1,即 \(k=p^a\) 时,判断 \(n\) 是否是 \(p\) 的质数。
  • \(k\) 的质因子数为 2 时,记这两个质因子分别为 \(a,b\),那用裴蜀定理判断 \(ax+by=n\) 是否存在非负整数解即可。

#include<bits/stdc++.h>
#define int __int128
using namespace std;
char buf[1000005],*p1,*p2;
#define gc() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1000000,stdin),p1==p2)?EOF:*p1++)
int read(){
	int x=0,f=0,c=gc();
	while(!isdigit(c))f|=(c=='-'),c=gc();
	while(isdigit(c))x=(x<<1)+(x<<3)+(c^48),c=gc();
	return f?-x:x;
}
int exgcd(int a,int b,int &x,int &y){
	if(b==0){
		x=1,y=0;
		return a;
	}
	int gcd=exgcd(b,a%b,x,y);
	int t=y;
	y=x-(a/b)*y;
	x=t;
	return gcd;
}
int T,n,k,tot;
map<int,int>M;
vector<int>ps[55];
int dis[55][100005],sum[55],p[55];
bool vis[100005];
struct node{
	int x,k;
	bool operator<(const node &t)const{return k>t.k;}
};
priority_queue<node>q;
void D(int id){
	for(int i=0;i<p[id];i++)dis[id][i]=1.5e18;
	memset(vis,0,sizeof(vis));
	dis[id][0]=0;
	q.push(node({0,0}));
	while(!q.empty()){
		node tmp=q.top();
		q.pop();
		if(vis[tmp.x])continue;
		vis[tmp.x]=1;
		for(int i=1;i<sum[id];i++){
			int v=(tmp.x+ps[id][i])%p[id],w=ps[id][i];
			if(dis[id][v]>dis[id][tmp.x]+w){
				dis[id][v]=dis[id][tmp.x]+w;
				q.push(node({v,dis[id][v]}));
			}
		}
	}
}
signed main()
{
	T=read();
	while(T--){
		n=read(),k=read();
		if(k==1){
			puts("NO");
			continue;
		}
		int id;
		if(!M.count(k)){
			id=M[k]=++tot;
			for(int i=2;i*i<=k;i++){
				if(k%i==0){
					ps[tot].push_back(i);
					while(k%i==0)k/=i;
				}
			}
			if(k>1)ps[tot].push_back(k);
			sum[tot]=ps[tot].size();
			p[tot]=ps[tot][0];
			if(sum[tot]>2){
				D(id);
			}
		}
		else id=M[k];
//		cerr<<id<<' '<<sum[id]<<endl;
		if(sum[id]==1)puts(n%p[id]==0?"YES":"NO");
		else if(sum[id]==2){
			int a=ps[id][0],b=ps[id][1],x,y;
			exgcd(a,b,x,y);
			x*=n;
			y*=n;
//			print(x),pc(' '),print(y),pc(10);
			int l=(-x+b-1)/b,r=y/a;
//			print(l),pc(' '),print(r),pc(10);
			puts(l<=r?"YES":"NO");
		}
		else{
			puts(n>=dis[id][n%p[id]]?"YES":"NO");
		}
	}
	return 0;
}

CF571E Geometric Progressions

给定 \(n\) 以及 \(n\) 个正整数对 \(a_i, b_i\)

\(i\)\(a_i, b_i\) 确定了一个序列 \(\{a_i, a_i b_i, a_i b_i^2, a_i b_i^3, \ldots \}\)

询问最小的在 \(n\) 个序列中都有出现的数,或者判断不存在。

\(n \le 100\)\(a_i, b_i \le {10}^9\),答案对 \({10}^9 + 7\) 取模。


我们先特判掉存在 \(b_i=1\) 的情况。

然后考虑将所有的 \(a_i,b_i\) 分解为质数的幂的乘积的形式。可以发现总共只有不超过 2000 种质数。

我们记 \(m\) 为不同的质数的数量,\(x_{i,j},y_{i,j}\) 分别为 \(a_i,b_i\) 分解后第 \(j\) 个质数的指数。

随后我们考虑依次将 \((a_i,b_i)\)\((a_{i+1},b_{i+1})\) 合并。即我们要找到一组 \((c_i,c_i+1)\) 满足 \(c_i,c_{i+1}\in \N\)\(\forall 1\le j\le m,x_{i,j}+y_{i,j}\cdot c_i=x_{i+1,j}+y_{i+1,j}\cdot c_{i+1}\)。容易发现这是一个方程组。

高斯消元后如果发现方程组:

  • 无解则表明原问题无解。
  • 存在唯一解就判断它对应的数是否满足后续所有的数列
  • 如果解出来发现 \(c_i\equiv X\pmod Y\),就让 \((a_i\cdot b_i^X,b_i^Y)\) 代替 \((a_i,b_i),(a_{i+1},b_{i+1})\) 继续与后面合并,注意特殊处理一下 \(X=0\) 时的情况。

#include<bits/stdc++.h>
#define int long long
using namespace std;
char buf[1000005],*p1,*p2;
#define gc() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1000000,stdin),p1==p2)?EOF:*p1++)
int read(){
	int x=0,f=0,c=gc();
	while(!isdigit(c))f|=(c=='-'),c=gc();
	while(isdigit(c))x=(x<<1)+(x<<3)+(c^48),c=gc();
	return f?-x:x;
}
char puf[1000005];
int ptot;
#define pc(x) (ptot==1000000?(fwrite(puf,1,1000000,stdout),ptot=0,puf[ptot++]=x):puf[ptot++]=x)
void print(int x){
	if(x<0){
		pc('-');
		print(-x);
		return;
	}
	if(x>9)print(x/10);
	pc(x%10+'0');
}
const int mod=1e9+7;
int exgcd(int a,int b,int &x,int &y){
	if(b==0){
		x=1,y=0;
		return a;
	}
	int gcd=exgcd(b,a%b,x,y);
	int t=y;
	y=x-(a/b)*y;
	x=t;
	return gcd;
}
int n,tot,p[2005],a[105],b[105],x[105][2005],y[105][2005];
int aa[2005][5];
struct node{
	int x,y,mx,my;
};
int fpow(int a,int b){
	int res=1;
	while(b){
		if(b&1)res=res*a%mod;
		a=a*a%mod;
		b>>=1;
	}
	return res;
}
node G(){
	for(int i=1;i<=2;i++){
		int k=0;
		for(int j=i;j<=tot;j++){
			if(aa[j][i]!=0){
				k=j;
				break;
			}
		}
		if(!k)continue;
		swap(aa[i][1],aa[k][1]);
		swap(aa[i][2],aa[k][2]);
		swap(aa[i][3],aa[k][3]);
		for(int j=1;j<=tot;j++){
			if(j!=i){
				int gcd=__gcd(aa[i][i],aa[j][i]),x=aa[i][i]/gcd,y=aa[j][i]/gcd;
				for(int l=1;l<=3;l++){
					aa[j][l]*=x;
					aa[j][l]-=aa[i][l]*y;
				}
			}
		}
	}
	int X=-1e10,Y=-1e10;
	for(int i=tot;i>=1;i--){
		if(aa[i][1]==0&&aa[i][2]==0){
			if(aa[i][3]){
				puts("-1");
				exit(0);
			}
		}
		else if(aa[i][1]==0){
			if(aa[i][3]%aa[i][2]){
				puts("-1");
				exit(0);
			}
			int yy=aa[i][3]/aa[i][2];
			if(Y!=-1e10&&Y!=yy){
				puts("-1");
				exit(0);
			}
			Y=yy;
		}
		else if(aa[i][2]==0){
			if(aa[i][3]%aa[i][1]){
				puts("-1");
				exit(0);
			}
			int xx=aa[i][3]/aa[i][1];
			if(X!=-1e10&&X!=xx){
				puts("-1");
				exit(0);
			}
			X=xx;
		}
		else{
			int xx=0,yy=0;
			int gcd=exgcd(aa[i][1],aa[i][2],xx,yy);
			if(aa[i][3]%gcd){
				puts("-1");
				exit(0);
			}
			xx*=aa[i][3]/gcd;
			yy*=aa[i][3]/gcd;
			int tx=aa[i][2]/gcd,ty=aa[i][1]/gcd;
			tx=abs(tx),ty=ty;
			xx=(xx%tx+tx)%tx;
			yy=(yy%ty+ty)%ty;
			return node({xx,yy,tx,ty});
		}
	}
	return node({X,Y});
}
void check(){
	int A=-1;
	for(int i=1;i<=n;i++){
		if(b[i]==1){
			if(A!=-1&&A!=a[i]){
				puts("-1");
				exit(0);
			}
		}
	}
	if(A!=-1){
		for(int i=1;i<=n;i++){
			if(A%a[i]){
				puts("-1");
				exit(0);
			}
			int B=A/a[i];
			while(B%b[i]==0)B/=b[i];
			if(B!=1){
				puts("-1");
				exit(0);
			}
		}
	}
}
signed main()
{
	n=read();
	for(int i=1;i<=n;i++)a[i]=read(),b[i]=read();
	check();
	for(int i=1;i<=n;i++){
		int x=a[i];
		for(int j=2;j*j<=x;j++){
			if(x%j==0){
				p[++tot]=j;
				while(x%j==0)x/=j;
			}
		}
		if(x>1)p[++tot]=x;
		x=b[i];
		for(int j=2;j*j<=x;j++){
			if(x%j==0){
				p[++tot]=j;
				while(x%j==0)x/=j;
			}
		}
		if(x>1)p[++tot]=x;
	}
	sort(p+1,p+tot+1);
	tot=unique(p+1,p+tot+1)-p-1;
	for(int i=1;i<=n;i++){
		int k=a[i];
		for(int j=2;j*j<=k;j++){
			if(k%j==0){
				int l=lower_bound(p+1,p+tot+1,j)-p;
				while(k%j==0)k/=j,x[i][l]++;
			}
		}
		if(k>1)x[i][lower_bound(p+1,p+tot+1,k)-p]++;
		k=b[i];
		for(int j=2;j*j<=k;j++){
			if(k%j==0){
				int l=lower_bound(p+1,p+tot+1,j)-p;
				while(k%j==0)k/=j,y[i][l]++;
			}
		}
		if(k>1)y[i][lower_bound(p+1,p+tot+1,k)-p]++;
	}
	for(int i=1;i<n;i++){
		for(int j=1;j<=tot;j++){
			aa[j][1]=y[i][j];
			aa[j][2]=-y[i+1][j];
			aa[j][3]=x[i+1][j]-x[i][j];
		}
		node sss=G();
		if(sss.mx==0){
			for(int j=1;j<=tot;j++)x[i][j]+=sss.x*y[i][j];
			bool f=1;
			for(int k=i+1;k<=n;k++){
				for(int j=1;j<=tot;j++){
					if(y[k][j]){
						if(x[i][j]<x[k][j]||(x[i][j]-x[k][j])%y[k][j]){
							f=0;
							j=tot+1;k=n+1;
							break;
						}
					}
					else if(x[i][j]!=x[k][j]){
						f=0;
						j=tot+1;k=n+1;
						break;
					}
				}
			}
			if(f){
				int ans=1;
				for(int j=1;j<=tot;j++)ans=ans*fpow(p[j],x[i][j])%mod;
				print(ans);
				fwrite(puf,1,ptot,stdout);
				return 0;
			}
			else{
				puts("-1");
				exit(0);
			}
		}
		else{
			if(sss.x==0&&x[i][1]<x[i+1][1])sss.x+=sss.mx;
			for(int j=1;j<=tot;j++)x[i][j]+=sss.x*y[i][j];
			for(int j=1;j<=tot;j++)y[i][j]*=sss.mx;
			for(int j=1;j<=tot;j++)x[i+1][j]=x[i][j],y[i+1][j]=y[i][j];
		}
	}
	int ans=1;
	for(int j=1;j<=tot;j++)ans=ans*fpow(p[j],x[n][j])%mod;
	print(ans);
	fwrite(puf,1,ptot,stdout);
	return 0;
}

随机数列

过了再补,等一手高手的题解。

AT_tenka1_2019_e Polynomial Divisors

给定 \(N\) 次多项式 \(f(x)=a_N\times x^N+a_{N-1}\times x^{N-1}+\dots+a_0\) ,请求出所有满足以下条件的质数 \(p\) :对于任意整数 \(x\)\(f(x)\) 都是 \(p\) 的倍数。从小到大输出。


注意到对于一个质数 \(p\),有 \(a_ix^i\equiv a_ix^{i\bmod(p-1)}\pmod p\),所以我们可以 \(O(n)\)\(f(x)\) 转化为一个 \(\min(n,p-2)\) 次的多项式并判断其是否符合要求。

接下来我们对 \(a_0\) 的值进行分讨:

  • \(a_0\not=0\),则 \(f(0)=a_0\),所以符合条件的只有 \(a_0\) 的质因子。
  • \(a_0=0\),那我们设 \(g=\gcd\limits_{i=1}^na_i\),显然所有 \(g\) 是质因子是满足条件的。同时对于一些小于 \(n+1\) 的质数也有可能通过降幂改变系数让 \(p\) 是满足条件的。所以我们再暴力判断小于 \(n+1\) 的质数,并把合法的加入答案。

#include<bits/stdc++.h>
#define int long long
using namespace std;
char buf[1000005],*p1,*p2;
#define gc() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1000000,stdin),p1==p2)?EOF:*p1++)
int read(){
	int x=0,f=0,c=gc();
	while(!isdigit(c))f|=(c=='-'),c=gc();
	while(isdigit(c))x=(x<<1)+(x<<3)+(c^48),c=gc();
	return f?-x:x;
}
char puf[1000005];
int ptot;
#define pc(x) (ptot==1000000?(fwrite(puf,1,1000000,stdout),ptot=0,puf[ptot++]=x):puf[ptot++]=x)
void print(int x){
	if(x<0){
		pc('-');
		print(-x);
		return;
	}
	if(x>9)print(x/10);
	pc(x%10+'0');
}
int n,a[10005],b[10005];
vector<int>ans;
void ch(int x){
	for(int i=0;i<=min(x,n);i++)b[i]=0;
	for(int i=0;i<=n;i++)b[i%(x-1)]=(b[i%(x-1)]+a[i]%x+x)%x;
	for(int i=0;i<=min(x,n);i++){
		if(b[i])return;
	}
	ans.push_back(x);
}
int p[10005],tot;
bool f[100005];
void ss(){
	for(int i=2;i<=10000;i++){
		if(!f[i])p[++tot]=i;
		for(int j=1;i*p[j]<=10000&&j<=tot;j++){
			f[i*p[j]]=1;
			if(i%p[j]==0)break;
		}
	}
}
signed main()
{
	n=read();
	for(int i=n;i>=0;i--)a[i]=read();
	if(a[0]!=0){
		int x=abs(a[0]);
		for(int i=2;i*i<=x;i++){
			if(x%i==0){
				ch(i);
				while(x%i==0)x/=i;
			}
		}
		if(x>1)ch(x);
	}
	else{
		ss();
		int gcd=0;
		for(int i=0;i<=n;i++)gcd=__gcd(gcd,a[i]);
		gcd=abs(gcd);
		for(int i=2;i*i<=gcd;i++){
			if(gcd%i==0){
				if(i>n)ans.push_back(i);
				while(gcd%i==0)gcd/=i;
			}
		}
		if(gcd>1&&gcd>n)ans.push_back(gcd);
		for(int i=1;i<=tot&&p[i]<=n;i++){
			ch(p[i]);
		}
		sort(ans.begin(),ans.end());
	}
	for(int x:ans)print(x),pc(10);
	fwrite(puf,1,ptot,stdout);
	return 0;
}

[WC2021] 斐波那契

众所周知,小葱同学擅长计算,尤其擅长计算组合数。但是对组合数有了充分研究的小葱同学对组合数失去了兴趣,而开始研究数列。

我们定义 \(F_0 = a\)\(F_1 = b\)\(F_i = (F_{i-1} + F_{i-2}) \bmod m\)\(i \ge 2\))。

现在给定 \(n\) 组询问,对于每组询问请找到一个最小的整数 \(p\),使得 \(F_p = 0\)


下午再写。

跳跳棋

跳跳棋是在一条数轴上进行的。棋子只能摆在整点上。每个点不能摆超过一个棋子。我们用跳跳棋来做一个简单的游戏:棋盘上有三颗棋子,分别在 \(a,b,c\) 这三个位置。我们要通过最少的跳动把他们的位置移动成 \(x,y,z\)(注意:棋子是没有区别的)。

跳动的规则很简单,任意选一颗棋子,对一颗中轴棋子跳动。跳动后两颗棋子距离不变。一次只允许跳过一颗棋子。

写一个程序,首先判断是否可以完成任务。如果可以,输出最少需要的跳动次数。

qhlkjrig.png


容易发现一个局面下最多只有 3 种不同的操作选择:将中间的向两边跳或者将某一边的棋子向中间跳。

常识告诉我们,这表明从一个局面出发能到达的所有局面构成一颗二叉树,而根代表的局面就是满足 \(b-a=c-b\) 的一个局面。

然后按题意模拟去找初末局面的 LCA 即可。


#include<bits/stdc++.h>
#define int long long
using namespace std;
char buf[1000005],*p1,*p2;
#define gc() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1000000,stdin),p1==p2)?EOF:*p1++)
int read(){
	int x=0,f=0,c=gc();
	while(!isdigit(c))f|=(c=='-'),c=gc();
	while(isdigit(c))x=(x<<1)+(x<<3)+(c^48),c=gc();
	return f?-x:x;
}
char puf[1000005];
int ptot;
#define pc(x) (ptot==1000000?(fwrite(puf,1,1000000,stdout),ptot=0,puf[ptot++]=x):puf[ptot++]=x)
void print(int x){
	if(x<0){
		pc('-');
		print(-x);
		return;
	}
	if(x>9)print(x/10);
	pc(x%10+'0');
}
int a,b,c,x,y,z;
deque<string>q1,q2;
string get(int a,int b,int c){
	return to_string(a)+"#"+to_string(b)+"#"+to_string(c);
}
string Get(int a,int b,int c,int l){
	while(b-a!=c-b){
		int l1=b-a,l2=c-b;
		if(l1<l2){
			int l3=(l2-1)%l1+1;
			int tt=(l2-l3)/l1;
			if(l<=tt){
				l3=l2-l*l1;
				b=c-l3;
				a=b-l1;
				break;
			}
			l-=tt;
			b=c-l3;
			a=b-l1;
		}
		else{
			int l3=(l1-1)%l2+1;
			int tt=(l1-l3)/l2;
			if(l<=tt){
				l3=l1-l*l2;
				b=a+l3;
				c=b+l2;
				break;
			}
			l-=tt;
			b=a+l3;
			c=b+l2;
		}
	}
	return get(a,b,c);
}
signed main()
{
	a=read(),b=read(),c=read(),x=read(),y=read(),z=read();
	if(a>b)swap(a,b);
	if(b>c)swap(b,c);
	if(a>b)swap(a,b);
	if(b>c)swap(b,c);
	if(x>y)swap(x,y);
	if(y>z)swap(y,z);
	if(x>y)swap(x,y);
	if(y>z)swap(y,z);
	int A=a,B=b,C=c,X=x,Y=y,Z=z;
	q1.push_front(get(a,b,c));
	int S=0,T=0;
	while(b-a!=c-b){
		int l1=b-a,l2=c-b;
		if(b-a<c-b){
			int l3=(l2-1)%l1+1;
			S+=(l2-l3)/l1;
			b=c-l3;
			a=b-l1;
			l2=l3;
		}
		else{
			int l3=(l1-1)%l2+1;
			S+=(l1-l3)/l2;
			b=a+l3;
			c=b+l2;
			l1=l3;
		}
		q1.push_front(get(a,b,c));
	}
	q2.push_front(get(x,y,z));
	while(y-x!=z-y){
		int l1=y-x,l2=z-y;
		if(y-x<z-y){
			int l3=(l2-1)%l1+1;
			T+=(l2-l3)/l1;
			y=z-l3;
			x=y-l1;
			l2=l3;
		}
		else{
			int l3=(l1-1)%l2+1;
			T+=(l1-l3)/l2;
			y=x+l3;
			z=y+l2;
			l1=l3;
		}
		q2.push_front(get(x,y,z));
	}
	if(q1[0]!=q2[0])puts("NO");
	else{
		int l=0,r=min(S,T),mid,res=S+T;
		while(l<=r){
			mid=l+r>>1;mid))l=mid+1,res=S-mid+T-mid;
			else r=mid-1;
		}
		pc('Y'),pc('E'),pc('S'),pc(10);
		print(res);
	}
	fwrite(puf,1,ptot,stdout);
	return 0;
}

小凯的疑惑

给定正整数 \(a,b\) 满足 \((a,b)=1\),求最大的 \(n\) 满足 \(ax+by=n\) 不存在非负整数解。


简单题。

因为 \(ax+by=n\),所以 \(n\equiv ax\pmod b\),因为前面的要求,我们有 \(n<ax\),那么有 \(n=ax-b\),为了让 \(n\) 最大,我们取 \(x=b-1\),有 \(n=ab-a-b\)


#include<bits/stdc++.h>
#define int long long
using namespace std;
char buf[1000005],*p1,*p2;
#define gc() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1000000,stdin),p1==p2)?EOF:*p1++)
int read(){
	int x=0,f=0,c=gc();
	while(!isdigit(c))f|=(c=='-'),c=gc();
	while(isdigit(c))x=(x<<1)+(x<<3)+(c^48),c=gc();
	return f?-x:x;
}
char puf[1000005];
int ptot;
#define pc(x) (ptot==1000000?(fwrite(puf,1,1000000,stdout),ptot=0,puf[ptot++]=x):puf[ptot++]=x)
void print(int x){
	if(x<0){
		pc('-');
		print(-x);
		return;
	}
	if(x>9)print(x/10);
	pc(x%10+'0');
}
int a,b;
signed main()
{
	a=read();b=read();
	if(a==1||b==1)puts("0");
	else print(a*b-a-b);
	fwrite(puf,1,ptot,stdout);
	return 0;
}

AGC063D Many CRT

给定系数 \(N,a,b,c,d\ (2\le N\le10^6,1 \le a,b,c,d \le 10^6)\),询问同余方程组 \(x \equiv a+kb\ (mod \ c + kd)\) 的最小非负整数解 \(x\),其中 \(0\le k<N\)

由于答案较大,请输出答案对 998244353 取模的结果。


\(g=\gcd(c,d)\)

因为 \(x\equiv a\pmod c\),也就有 \(x\equiv a\pmod g\)。结合 \(x\equiv a+ib\pmod {c+id}\),可以发现如果 \(g\nmid b\),原方程组无解。

我们设

posted @ 2024-12-03 19:56  Grisses  阅读(13)  评论(0)    收藏  举报
Document