Noip2009 Hankson的趣味题

Description

Hanks 博士是BT (Bio-Tech,生物技术) 领域的知名专家,他的儿子名叫Hankson。现在,刚刚放学回家的Hankson 正在思考一个有趣的问题。
今天在课堂上,老师讲解了如何求两个正整数\(c_1\)\(c_2\) 的最大公约数和最小公倍数。现在Hankson 认为自己已经熟练地掌握了这些知识,他开始思考一个“求公约数”和“求公倍数”之类问题的“逆问题”,这个问题是这样的:已知正整数\(a_0\),\(a_1\),\(b_0\),\(b_1\),设某未知正整数x 满足:
1.\(x\)\(a_0\) 的最大公约数是\(a_1\)
2.\(x\)\(b_0\) 的最小公倍数是\(b_1\)
Hankson 的“逆问题”就是求出满足条件的正整数x。但稍加思索之后,他发现这样的\(x\) 并不唯一,甚至可能不存在。因此他转而开始考虑如何求解满足条件的\(x\) 的个数。请你帮助他编程求解这个问题。

Input

第一行为一个正整数\(n\),表示有\(n\) 组输入数据。接下来的n 行每行一组输入数据,为四个正整数\(a_0\)\(a_1\)\(b_0\)\(b_1\),每两个整数之间用一个空格隔开。输入数据保证\(a_0\) 能被\(a_1\) 整除,\(b1\) 能被\(b_0\) 整除。

Output

每组输入数据的输出结果占一行,为一个整数。
对于每组数据:若不存在这样的 \(x\),请输出0;
若存在这样的 \(x\),请输出满足条件的\(x\)的个数;

Sample Input

2 
41 1 96 288 
95 1 37 1776 

Sample Output

6
2

Hint

【说明】
第一组输入数据,\(x\) 可以是9、18、36、72、144、288,共有6 个。
第二组输入数据,\(x\) 可以是48、1776,共有2 个。
【数据范围】
对于 50%的数据,保证有1≤\(a_0\)\(a_1\)\(b_0\)\(b_1\)≤10000 且n≤100。
对于 100%的数据,保证有1≤\(a_0\)\(a_1\)\(b_0\)\(b_1\)≤2,000,000,000 且n≤2000。

题解

90分算法

我们把题目的内容表示一下

\[\left\{ \begin{align*} gcd(a_0,x)&=a_1\\ lcm(b_0,x)&=b_1\\ \end{align*} \right. \]

显然,x一定是\(b_1\)的因数,一定是\(a_1\)的倍数。假设m表示\(a_0\)\(a_1\)\(b_0\)\(b_1\)的大小,我们可以\(O(\sqrt m)\)枚举\(b_1\)的约数x,然后判断x满不满足上面的方程组即可。由于gcd和lcm的时间复杂度都是\(O(\log m)\),有\(n\)次询问,所以总时间复杂度为\(O(n \sqrt m \log m)\)

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;

template<typename A>inline void read(A&a){a=0;A f=1;int c=0;while(c<'0'||c>'9'){c=getchar();if(c=='-')f*=-1;}while(c>='0'&&c<='9'){a=a*10+c-'0';c=getchar();}a*=f;}
template<typename A,typename B>inline void read(A&a,B&b){read(a);read(b);}
template<typename A,typename B,typename C>inline void read(A&a,B&b,C&c){read(a);read(b);read(c);}

typedef long long ll;

int T;
int a0,a1,b0,b1,ans;

inline int gcd(int x,int y){return x%y==0?y:gcd(y,x%y);}
inline int lcm(int x,int y){return x/gcd(x,y)*y;}
inline bool check(int x){return gcd(x,a0)==a1&&lcm(x,b0)==b1;}

void Init(){
    read(a0,a1);
    read(b0,b1);
    ans=0;
}

void Work(){
    int t=sqrt(b1);
    for(register int i=1;i<=t;++i){
        if(!(b1%i)){
            if(check(i))++ans;
            if(b1/i!=i)if(check(b1/i))++ans;
        }
    }
    printf("%d\n",ans);
}

int main(){
    read(T);
    while(T--){
        Init();
        Work();
    }
    return 0;
} //鉴于这个代码是我一年前写的,所以如果码风不大正常还请见谅。

这个算法在当年Noip的时候可以拿90分,但是在洛谷神机和codevs里面就可以过了。

100分做法

我们把上面的式子再放一遍。

\[\left\{ \begin{align*} gcd(a_0,x)&=a_1\\ lcm(b_0,x)&=b_1\\ \end{align*} \right. \]

因为\(x\)\(b_1\)的约数,所以x的质因子一定是\(b_1\)拥有的。所以我们可以对于p的每一个质因子,计算x可以拥有多少个。
我们枚举\(b_1\)的质因数p,设\(a_0\)\(a_1\)\(b_0\)\(b_1\)的分别有\(m_{a_0}\)\(m_{a_1}\)\(m_{b_0}\)\(m_{b_1}\)个,x有\(m_x\)个。
对于\(gcd(a_0,x)=a_1\)这个式子,我们有三种情况:

\[\left\{ \begin{align*} m_{a_0} > m_{a_1} \quad &\Rightarrow \quad m_x = m_{a_1}\\ m_{a_0} = m_{a_1} \quad &\Rightarrow \quad m_x \geq m_{a_1}\\ m_{a_0} < m_{a_1} \quad &\Rightarrow \quad \text{无解}\\ \end{align*} \right. \]

对于\(lcm(b_0,x)=b_1\)这个式子,我们也有如下三种情况:

\[\left\{ \begin{align*} m_{b_0} > m_{b_1} \quad &\Rightarrow \quad \text{无解}\\ m_{b_0} = m_{b_1} \quad &\Rightarrow \quad m_x \leq m_{b_1}\\ m_{b_0} < m_{b_1} \quad &\Rightarrow \quad m _x = m_{b_1}\\ \end{align*} \right. \]

那么我们现在把上面集中情况整合一下:有如下几种情况:

\[\left\{ \begin{align*} m_{a_0} > m_{a_1} , m_{b_0} < m_{b_1} , m_{a_1} = m_{b_1} \qquad &\Rightarrow \qquad m_x\text{有$1$中取法}\\ m_{a_0} > m_{a_1} , m_{b_0} = m_{b_1} , m_{a_1} \leq m_{b_0} \qquad &\Rightarrow \qquad m_x\text{有$1$中取法}\\ m_{a_0} = m_{a_1} , m_{b_0} < m_{b_1} , m_{a_0} \leq m_{b_1} \qquad &\Rightarrow \qquad m_x\text{有$1$中取法}\\ m_{a_0} = m_{a_1} , m_{b_0} = m_{b_1} , m_{a_1} \leq m_{b_1} \qquad &\Rightarrow \qquad m_x\text{有$m_{b_1}-m_{a_1}+1$中取法}\\ \text{其他情况} \qquad &\Rightarrow \qquad \text{无解}\\ \end{align*} \right. \]

我们把\(cnt_p\)表示\(m_x\)的取值个数,(无解即为0),那么我们只要求出

\[\prod \limits_{\text{质数}p|d}{} cnt_p \]

即可。

我们可以先用线性筛筛出\(1..\sqrt{2 \times 10^9}\)的质数,然后对于每一次询问,枚举每个质数,判断能否整除\(b_1\),如果能整除,那么我们就按照上面的标准计算并累乘\(cnt_p\)。最后如果\(b_1 \neq 1\),那么我们在对剩下来这个质数\(b_1\)再计算一次。
由于最小的10个质数\(2,3,5,7,11,13,17,19,23,29,31\)的乘积已经超过\(2 \times 10^9\),所以能整除\(b_1\)的质数不会超过10个。由于在\(1..d\)的范围内有\(\frac {d}{\ln d}\)个,所以该算法的时间复杂度为\(O(\frac{n\sqrt{m}}{\ln \sqrt m})\)。这样就不会超时了,该算法可以获得100分。

#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
using namespace std;

const int maxn=2000+7,maxm=2e9;
int n,a0,a1,b0,b1,ans,p[45000],np[45000],tot;

inline void Prime(){
	np[0]=np[1]=1;int t=sqrt(maxm);
	for(register int i=2;i<=t;++i){
		if(!np[i])p[++tot]=i;
		for(register int j=1;j<=tot&&i*p[j]<=t;++j){
			np[i*p[j]]=1;
			if(i%p[j]==0)break;
		}
	}
}

void Process(int p){
	int a0m=0,a1m=0,b0m=0,b1m=0;
	while(a0%p==0)++a0m,a0/=p;
	while(a1%p==0)++a1m,a1/=p;
	while(b0%p==0)++b0m,b0/=p;
	while(b1%p==0)++b1m,b1/=p;
	if(a0m==a1m&&b0m==b1m&&a1m<=b1m)ans*=(b1m-a1m+1);
	else if((a0m>a1m&&b0m<b1m&&a1m==b1m)||(a0m>a1m&&b0m==b1m&&a1m<=b0m)||(a0m==a1m&&b0m<b1m&&a0m<=b1m))return;
	else ans=0;
}

int main(){
	scanf("%d",&n);Prime();
	while(n--){
		scanf("%d%d%d%d",&a0,&a1,&b0,&b1);ans=1;
		for(register int i=1;i<=tot;++i){
			if(b1%p[i]==0)Process(p[i]);
			if(!ans)break;
		}
		if(ans&&b1!=1)Process(b1);
		printf("%d\n",ans);
	}
}
posted @ 2018-06-30 16:45  hankeke303  阅读(317)  评论(0编辑  收藏  举报