总结:
1.如果lcm(b,x) == d,(b,x的最小公倍数 == d), 则应该知道, b, x 都为d的约数。
2.而当b,x都为d的约数的时候,则b 以及 x 中的 质因子, 在 d这个最小公倍数中都存在。
3.约数的个数:n的约数个数的上界为sqrt(n),也就是最多为sqrt(n)个约数,但是实际上,1~n中平均下来的话,每个数的约数大概只有(log n)个。
4.在1e9以内,约数个数最多个值它的约数也就1536个约数
5.由3,4就可以知道,如果有多组测试数据,求n的约数, 实际上我们可以先预处理出 sqrt(max(n) ), 根据题目的要求,n最大的取值下的sqrt(n)的质数全部筛出来.
然后直接对n 用 预处理出的质数 筛出对应于当前n的各个质因数 以及 指数。(实际上按照之前反素数那道题目所说,实际上n的质因数不会超过11个。题解链接:反素数题解)
而筛出了对应的质因数 以及 指数 以后,直接通过dfs()算出各个约数,存起来即可。
6. 同时最大公约数 以及 最小公倍数中还会常用质因数 与它两之间的关系进行求解。(之前的最大公约数和最小公倍数的问题就用到了这个方式进行求解)。题解链接:https://blog.youkuaiyun.com/qq_49120553/article/details/119078625?spm=1001.2014.3001.5502
如果a的素因数分解为 (x1 ^ p1) * (x2 ^ p2) * (x3 ^ p3)
b的素因数分解为 (x1 ^ p4) * (x5 ^ p5) * (x6 ^ p6)
可以增添一下素因数a : (x1 ^ p1) * (x2 ^ p2) * (x3 ^ p3) *(x5 ^ 0) * (x6 ^ 0)
b:(x1 ^ p4) * (x5 ^ p5) * (x6 ^ p6)* (x2 ^ 0) * (x3 ^ 0)
则a,b的最大公约数为:(x1 ^ min(p1,p4) ) * (x2 ^ min (p2 ,0) ) * (x3 ^ min(p3,0) ) * (x5 ^ min(0,p5)) * (x6 ^ min(0.p6) ).
a,b的最小公倍数为:(x1 ^ max(p1,p4) ) * (x2 ^ max (p2 ,0) ) * (x3 ^ max(p3,0) ) * (x5 ^ max(0,p5)) * (x6 ^ max(0.p6) ).
题目链接:https://ac.nowcoder.com/acm/problem/16610
题目:
题目描述
Hanks博士是BT(Bio-Tech,生物技术)领域的知名专家,他的儿子名叫Hankson。现在,刚刚放学回家的Hankson正在思考一个有趣的问题。
今天在课堂上,老师讲解了如何求两个正整数c1和c2的最大公约数和最小公倍数。现在Hankson认为自己已经熟练地掌握了这些知识,他开始思考一个“求公约数”和“求公倍数”之类问题的“逆问题”,这个问题是这样的:已知正整数a0,a1,b0,b1,设某未知正整数x满足:
1、 x和a0的最大公约数是a1;
2、 x和b0的最小公倍数是b1。
Hankson的“逆问题”就是求出满足条件的正整数x。但稍加思索之后,他发现这样的x并不唯一,甚至可能不存在。因此他转而开始考虑如何求解满足条件的x的个数。请你帮助他编程求解这个问题。
输入描述:
第一行为一个正整数n,表示有n组输入数据。接下来的n行每行一组输入数据,为四个正整数a0,a1,b0,b1,每两个整数之间用一个空格隔开。输入数据保证a0能被a1整除,b1能被b0整除。
输出描述:
对于每组数据:若不存在这样的x,请输出0; 若存在这样的x,请输出满足条件的x的个数;示例1
输入
复制2 41 1 96 288 95 1 37 1776
2 41 1 96 288 95 1 37 1776输出
复制6 2
6 2说明
第一组输入数据,x可以是9、18、36、72、144、288,共有6个。 第二组输入数据,x可以是48、1776,共有2个。备注:
对于50%的数据,保证有1≤a0,b1,b0,b1≤10000且n≤100。
对于100%的数据,保证有1≤a0,b1,b0,b1≤2,000,000,000且n≤2000。
分析1:
由结论可以知道,lcm(x,b0) == b1,则x为b1的约数。
那么我们可以直接去找b1的所有约数,然后去判断这个约数x是否满足我们的条件(gcd(x,a0) == a1 && lcm(x,b0) == b1)
如果满足条件,那么就统计值total++。最后输出即可。
但是,要注意,如果直接使用试除法求是否为约数的话,时间复杂度会变为n*sqrt(b1) * log b1. 会超时。
所以就需要用到我们总结中的另一个求某个数约数的技巧,先将最大值的sqrt(n)内的质数筛出来。(就像之前的筛质因数一样,先筛sqrt(n),最后剩下的就是唯一一个大于sqrt(n)的值)。 然后通过筛出的质数,筛出sqrt(n)内满足条件的质数 和 它的指数。
最后通过dfs()求解出 其 对应的所有约数即可。(减少不能整除的情况)
代码实现:
90的代码:
# include <iostream>
using namespace std;
int gcd(int a , int b)
{
return b ? gcd(b,a % b) : a;
}
int main()
{
int loop;
scanf("%d",&loop);
while(loop--)
{
int a0,a1,b0,b1;
scanf("%d %d %d %d",&a0,&a1,&b0,&b1);
int cnt = 0;
for(int i = 1 ; i <= b1 / i ; i++)
{
if(b1 % i == 0)
{
int temp1 = i;
if(gcd(temp1,a0) == a1 && (long long)gcd(temp1,b0) * b1 == (long long)temp1 * b0)
{
cnt++;
}
if(b1 / i != i)
{
temp1 = b1 / i;
}
if(gcd(temp1,a0) == a1 && gcd(temp1,b0) * b1 == temp1 * b0)
{
cnt++;
}
}
}
printf("%d\n",cnt);
}
return 0;
}
ac代码:
# include <iostream>
# include <vector>
using namespace std;
const int N = 2e5 + 10;
int prim[N],cnt;
bool choose[N];
typedef pair<int,int> PII;
vector<PII> p;
int t; // 统计约数的个数
int yueshu[N];
int gcd(int a , int b)
{
return b ? gcd(b,a % b) : a;
}
void get_prim(int n)
{
for(int i = 2 ; i <= n ; i++)
{
if(!choose[i])
{
prim[++cnt] = i;
}
for(int j = 1 ; prim[j] <= n / i ; j++)
{
choose[i * prim[j]] = true;
if(i % prim[j] == 0)
{
break;
}
}
}
}
void dfs(int u , int i)
{
if(i == p.size())
{
yueshu[++t] = u;
return;
}
for(int j = 0 ; j <= p[i].second ; j++)
{
dfs(u, i + 1);
u = u * p[i].first; // 最后会多乘一次u,但是不影响,因为最后一个u没有用处
}
}
int main()
{
get_prim(N); // 对前 sqrt(2e9)筛质数进行预处理。
int loop;
scanf("%d",&loop);
while(loop--)
{
p.clear();
t = 0;
int a0,a1,b0,b1;
scanf("%d %d %d %d",&a0,&a1,&b0,&b1);
int d = b1;
//求b1的约数,先求出b1对应的质数,以及其对应的质数
for(int i = 1 ; prim[i] <= d / prim[i] ; i++) // 只需要先筛出 前sqrt(b1)对应的值,最后一个值就是大于sqrt(b1)的值了
{
if(d % prim[i] == 0)
{
int time = 0;
while(d % prim[i] == 0)
{
time++;
d /= prim[i];
}
p.push_back({prim[i],time});
}
}
if(d > 1) //最后剩下的就是大于sqrt(b1)的值了
{
p.push_back({d,1});
}
//上面一步将所有的b1的质数 以及 指数都整出来了。接下来求其对应的约数
dfs(1,0); // dfs(u,i) u代表当前的值,i代表到了第几个指数
//上面的dfs()整理出了所有的约数
int count = 0; // 求满足条件的个数
for(int i = 1 ; i <= t ; i++)
{
int x = yueshu[i];
if (gcd(x, a0) == a1 && (long long)x * b0 / gcd(x, b0) == b1)
{
count++ ;
}
}
printf("%d\n",count);
}
return 0;
}
分析2:
还有另一种方式,就是用我们总结6,gcd()和lcm()与质因数之间的关系进行求解。
由我们的分析1已经知道,x为d1的约数。 所以x中的质因数,在d1中必定全部都有。
所以我们算出d1的各个质因数 以及其 指数,以及 a0,a1的有关 d1质因数 以及对应的指数。
根据gcd()和lcm()与质因数之间的关系,总结出对应的关系。
其中每个所对应的无解情况,就是对应的a,b,c,d,取值是非法的,凑不出对应的x出来。
对应的值为0.
如果没有无解的情况,随后通过乘法原理将各个d1对应质因数下对应的值求解出最后的答案。
# include <iostream>
# include <vector>
using namespace std;
const int N = 2e5 + 10;
int prim[N],cnt;
bool choose[N];
void get_prim(int n)
{
for(int i = 2 ; i <= n ; i++)
{
if(!choose[i])
{
prim[++cnt] = i;
}
for(int j = 1 ; prim[j] <= n / i ; j++)
{
choose[i * prim[j]] = true;
if(i % prim[j] == 0)
{
break;
}
}
}
}
int main()
{
get_prim(N); // 对前 sqrt(2e9)筛质数进行预处理。
int loop;
scanf("%d",&loop);
while(loop--)
{
int a0,a1,b0,b1;
scanf("%d %d %d %d",&a0,&a1,&b0,&b1);
int a = a0;
int c = a1;
int b = b0;
int d = b1;
int count = 1;
bool flag = true;
for(int i = 1 ; prim[i] <= d / prim[i] ; i++)
{
int ma = 0,mc = 0,mb = 0,md = 0;
if(d % prim[i] == 0)
{
while(a % prim[i] == 0)
{
ma++;
a /= prim[i];
}
while(c % prim[i] == 0)
{
mc++;
c /= prim[i];
}
while(b % prim[i] == 0)
{
mb++;
b /= prim[i];
}
while(d % prim[i] == 0)
{
md++;
d /= prim[i];
}
if(ma == mc && mb == md && mc <= md)
{
count *= (md - mc + 1);
}
else if(ma > mc && mb < md && mc == md)
{
continue;
}
else if(ma > mc && mb == md && mc <= md)
{
continue;
}
else if(ma == mc && mb < md && mc <= md)
{
continue;
}
else
{
flag = false;
break;
}
}
}
if(flag && d > 1)
{
int ma = 0,mc = 0,mb = 0,md = 1;
while(a % d == 0)
{
ma++;
a /= d;
}
while(c % d == 0)
{
mc++;
c /= d;
}
while(b % d == 0)
{
mb++;
b /= d;
}
if(ma == mc && mb == md && mc <= md)
{
count *= (md - mc + 1);
}
else if(ma > mc && mb < md && mc == md)
{
count *= 1;
}
else if(ma > mc && mb == md && mc <= md)
{
count *= 1;
}
else if(ma == mc && mb < md && mc <= md)
{
count *= 1;
}
else
{
flag = false;
}
}
if(flag)
{
printf("%d\n",count);
}
else
{
printf("0\n");
}
}
return 0;
}