这一题最重要的就是数学推理!
初中蒟蒻不懂集合,只好奉上通俗易懂的推理
首先当然是得打素数表,实际上我们只要打到前4000个素数就可以了,多打无害。
那么,我们来推理。
假如a与b的最大公因数是c,我们把它们分解质因数,将a分解后的结果中含有素数p的个数记为an,将b分解后的结果中含有素数p的个数记为bn,将c分解后的结果中含有素数p的个数记为cn。
如果an=cn,那么bn只要大于cn大可随意取值
如果an>cn,那么bn必须等于cn(自己思考)
如果an<cn,那么这游戏没法玩了,直接输出0退出程序。
假如a与b的最小公倍数是c,我们把它们分解质因数,将a分解后的结果中含有素数p的个数记为an,将b分解后的结果中含有素数p的个数记为bn,将c分解后的结果中含有素数p的个数记为cn。
如果an=cn,那么bn只要小于cn并大于等于0,大可随意取值
如果an<cn,那么bn必须等于cn(自己思考)
如果an>cn,那么这游戏没法玩了,直接输出0退出程序。
那么,如果上述两项讨论中均出现an=cn的情况,我们就可以发现,bn只能取cn(第一项讨论中的)~cn(第二项讨论中的)之间的数。
将情况组合(这里直接相乘)即为解
好吧,推理结束,程序就好写了。我们只要写一个过程,将我们的推理结果表述一遍,并将素数表前4000的数据代到p里面去运算就好了。
AC代码奉上,仅供参考。
C++ 代码
#include<cstdio>
#include<iostream>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<string>
using namespace std;
int ssqrt;
int cf(int a,int b)//去掉a中与b共有的质因数。思想:将b质因数分解,同时将a中与b共有的质因数去掉。
{
ssqrt=sqrt(b);
for(int i=2;i<=ssqrt;i++)//sqrt(b)复杂度质因数分解b
{
if(b%i==0)while(a%i==0)a/=i;//去掉a中与b共有的质因数,将a分解
while(b%i==0)b/=i;//将b质因数分解
}
if(b!=1)while(a%b==0)a/=b;//注意:此时b可能还不是1,因为b可能有比sqrt(b)更大的质因数,但至多只有一个,且它的次幂至多是1。所以如果b不是1,那就只能是一个质数。于是此时继续分解a。
return a;//返回剩下的a
}
int gcd(int a,int b){return b==0?a:gcd(b,a%b);}//辗转相除求最大公约数
int main()
{
int a0,a1,b0,b1;
int gs;
int m,n,s,l,q;
scanf("%d",&gs);
int cnt;
while(gs--)
{
scanf("%d%d%d%d",&a0,&a1,&b0,&b1);
if(a0%a1|b1%b0|b1%a1){printf("0\n");continue;}//如果m、n、s中有小数,则直接输出0。这里的代码用到了一些位运算
m=a0/a1,n=b1/b0,s=b1/a1;l=cf(s,n);//求出m、n、s,然后求出l
if(gcd(max(s/l,m),min(s/l,m))!=1){printf("0\n");continue;}//如果不互质,则直接输出0
q=cf(l,m);cnt=0,ssqrt=sqrt(q);//求出q,开始枚举q的因数,求出q的因数个数
for(int i=1;i<=ssqrt;i++)if(q%i==0)cnt+=i==q/i?1:2;//这里注意,如果i==q/i,则只加1,否则加2
printf("%d\n",cnt);//输出
}
return 0;
}
这道题其实有一个优化算法,可以跑得更快。
并且,因为下面的题解似乎都没有很好地讲解这道题的思路,因此本人将详细地演示一下这道题的推算过程:
首先,我们知道这两个等式:(a0,x)=a1,[b0,x]=b1(a0,x)=a1,[b0,x]=b1
于是,我们可以设:x=a1p,b1=xtx=a1∗p,b1=x∗t
于是有:a1pt=b1a1∗p∗t=b1
所以我们令:b1/a1=sb1/a1=s
则:p*t=sp∗t=s
即:t=s/pt=s/p
又由最大公约数与最小公倍数的定义与性质可得:
(a0/a1,p)=1,(b1/b0,t)=1(a0/a1,p)=1,(b1/b0,t)=1
所以我们令:a0/a1=m,b1/b0=na0/a1=m,b1/b0=n
则有:(p,m)=1,(s/p,n)=1(p,m)=1,(s/p,n)=1
这就是第一个结论,我们称其为结论一。事实上,我们其实已经可以由结论一整理出可以AC的方法,即用sqrt(s)的复杂度枚举s的因数,然后将每个因数放到结论一中,看看是否成立,再统计所有符合结论一的因数的个数,然后输出即可。这种算法的复杂度是:O(sqrt(s)log(s)n)。这样其实也能卡过数据,但是还是没有达到理论上的通过。所以我们还要继续优化。
我们考虑(s/p,n)=1。如果s/p与n有相同质因数,则意思那个无法使(s/p,n)=1成立。于是,我们可以将s与n所有相同的质因数从s中去掉,得到剩余的数l(这一点还是很需要技巧的,在程序中会有解析)。于是,s/p就必须是l的约数。
我们继续考虑(p,m)=1。因为s/p是l的约数,那么p就一定可以表示为这样的形式:
p=(s/l)*rp=(s/l)∗r
即:p一定是s/l的倍数(因为s/p是l的约数)。而r也必须是l的约数。于是就又有:
r|l,(r,m)=1r∣l,(r,m)=1
这就是第二个结论,我们称其为结论二。而解决结论二的方法便很明显了。我们可以用与解决结论一相似的方法,将l与m所有相同的质因数从l中去掉,得到剩余的数q。那么这样,所有符合条件的r都是q的因数了。然后,我们可以用sqrt(q)的复杂度枚举q的所有因数,输出q的因数个数就行了。这样,复杂度便降到了:O((sqrt(s)+log(s))*n),从理论来说也不会超时了。
还有一点需要注意,那就是特判没有符合要求的x的情况。这种情况出现只有四种可能:
1、s不为整数
2、m不为整数
3、n不为整数
4、(s/l,m)≠1,即因为p是s/l的倍数,所以无论r取何值,都会有(p,m)≠1
加上这四个特判,这道题便做完了。
总结一下,这道题,代码虽不长,但是思路需要很精细,也难想到,是一道不错的题。
下为代码:
C++ 代码
#include<cstdio>
#include<iostream>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<string>
using namespace std;
int ssqrt;
int cf(int a,int b)//去掉a中与b共有的质因数。思想:将b质因数分解,同时将a中与b共有的质因数去掉。
{
ssqrt=sqrt(b);
for(int i=2;i<=ssqrt;i++)//sqrt(b)复杂度质因数分解b
{
if(b%i==0)while(a%i==0)a/=i;//去掉a中与b共有的质因数,将a分解
while(b%i==0)b/=i;//将b质因数分解
}
if(b!=1)while(a%b==0)a/=b;//注意:此时b可能还不是1,因为b可能有比sqrt(b)更大的质因数,但至多只有一个,且它的次幂至多是1。所以如果b不是1,那就只能是一个质数。于是此时继续分解a。
return a;//返回剩下的a
}
int gcd(int a,int b){return b==0?a:gcd(b,a%b);}//辗转相除求最大公约数
int main()
{
int a0,a1,b0,b1;
int gs;
int m,n,s,l,q;
scanf("%d",&gs);
int cnt;
while(gs--)
{
scanf("%d%d%d%d",&a0,&a1,&b0,&b1);
if(a0%a1|b1%b0|b1%a1){printf("0\n");continue;}//如果m、n、s中有小数,则直接输出0。这里的代码用到了一些位运算
m=a0/a1,n=b1/b0,s=b1/a1;l=cf(s,n);//求出m、n、s,然后求出l
if(gcd(max(s/l,m),min(s/l,m))!=1){printf("0\n");continue;}//如果不互质,则直接输出0
q=cf(l,m);cnt=0,ssqrt=sqrt(q);//求出q,开始枚举q的因数,求出q的因数个数
for(int i=1;i<=ssqrt;i++)if(q%i==0)cnt+=i==q/i?1:2;//这里注意,如果i==q/i,则只加1,否则加2
printf("%d\n",cnt);//输出
}
return 0;
}
直接拿别人的题解和代码不太好吧(一点都不带改的)…
我推论是看懂了……
然鹅代码完全看不懂
呜呜呜
Orz memset0
Orz memset0
Orz memset0