• P1072 Hankson 的趣味题[数论]


    题目描述

    Hanks 博士是 BT(Bio-Tech,生物技术) 领域的知名专家,他的儿子名叫 Hankson。现在,刚刚放学回家的 Hankson 正在思考一个有趣的问题。

    今天在课堂上,老师讲解了如何求两个正整数c_1c1 和 c_2c2 的最大公约数和最小公倍数。现在 Hankson 认为自己已经熟练地掌握了这些知识,他开始思考一个“求公约数”和“求公倍数”之类问题的“逆问题”,这个问题是这样的:已知正整数a_0,a_1,b_0,b_1a0,a1,b0,b1,设某未知正整数xx 满足:

    1. xx 和 a_0a0 的最大公约数是 a_1a1;

    2. xx 和 b_0b0 的最小公倍数是b_1b1。

    Hankson 的“逆问题”就是求出满足条件的正整数xx。但稍加思索之后,他发现这样的xx 并不唯一,甚至可能不存在。因此他转而开始考虑如何求解满足条件的 xx 的个数。请你帮助他编程求解这个问题。

    解析

    这道题很容易想出一个比较暴力但是可以AC的解法。

    根据题目,我们有(gcd(a_0,x)=a_1,lcm(x,b_0)=b_1),那么显然(a_1mid x,xmid b_1),于是我们找出(b_1)的所有质因子,然后验证一下(a_1mid x)是否成立即可。注意在找质因子的时候不要打暴力,肯定会T,应先筛个素数,再用素数凑出(b_1),再判断,卡卡常随便过。

    如果是在考场上,这种做法无疑是最优的,好想又好写。但是实际上这道题有更好的巧解做法。


    (lcm(x,b_0)=b_1)(xmid b_1),故一定存在一个(k),使得(kmid x ~&~ kmid b_1),即(x)的质因子一定是(b_1)的质因子。

    由此我们可以想到,我们不妨枚举所有(1sim sqrt{(2*10^9)})的所有素数(k),并不断将质因子(k)(a_0,a_1,b_0,b_1)中除去,同时我们可以通过每个数含有(k)的数量确定一些可行的答案。如果最终(b_1 ot= 1),根据素数理论,说明(b_1)本身就是质数。

    那么如何通过四个数所含有的(k)的数量来判断解的情况呢?

    (a_0,a_1,b_0,b_1)分别含有(ma,mb,mc,md)个质因子(k)(x)含有(mx)个质因子(k),那么我们可以进行如下讨论(可以类比朴素解法的检验过程):

    对于(gcd(x,a_0)=a_1)这一约束条件:

    1. (ma>mb)时,有一个解(mx=mb)
    2. (ma=mb)时,有解(mx>=mb)
    3. (ma<mb)时,无解。

    对于(lcm(x,b_0)=b_1)这一约束条件:

    1. (mc<md)时,有一个解(mx=md)
    2. (mc=md)时,有解(mx<=md)
    3. (mc>md)时,无解。

    故我们可以发现,只要(ma<mb~||~mc>md),我们就可以排除当前质因子(k)存在解的可能性。而当(mc=md,ma=mb,mc<=md)时,有(md-mb+1)个解。

    要明确一点,我们每次求解的方案数是在质因子(k)意义下的解,即(x)包含质因子(k)的方案数(有点难理解,好好思考)。最终的某个解(x’)一定是其中一些符合要求的质因子相乘得到的,因此根据乘法原理,对于每种符合要求的质因子(k)能取的方案数,我们累乘到答案中,即可的到所有可行质因子(k)最终构成所有(x)的数量。(仔细理解质因子(k)与答案(x)的关系)

    设对于质因子(k)(x)可能包含它的方案数为(cnt_k),则最终答案可以表示为:

    [prod_{k~mid b_1}~cnt_k ]

    该算法十分高效,却极其难想出来,可谓一种毒瘤解法。。。

    参考代码

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<cmath>
    #include<algorithm>
    #include<string>
    #include<cstdlib>
    #include<queue>
    #include<vector>
    #define INF 0x3f3f3f3f
    #define PI acos(-1.0)
    #define N 500010
    #define MAX 2000000000
    #define MOD 2520
    #define E 1e-12
    #define ll long long
    using namespace std;
    ll p[N],v[N],q,m;
    ll a0,a1,b0,b1,ans=1;
    inline void get(ll n)
    {
    	memset(v,0,sizeof(v));
    	m=0;
    	for(int i=2;i<=n;++i){
    		if(v[i]==0){v[i]=i;p[++m]=i;}
    		for(int j=1;j<=m;++j){
    			if(p[j]>n/i||p[j]>v[i]) break;
    			v[i*p[j]]=p[j];
    		}
    	}
    }
    inline void work(int p)
    {
    	ll ma=0,mb=0,mc=0,md=0;
    	while(!(a0%p)) a0/=p,ma++;
    	while(!(a1%p)) a1/=p,mc++;
    	while(!(b0%p)) b0/=p,mb++;
    	while(!(b1%p)) b1/=p,md++;
    	if(ma<mc||mb>md){ans=0;return;}
    	if(ma==mc&&mb==md){if(mc<=md)ans*=(md-mc+1);else ans=0;return;}
    	if(((ma>mc)&&(mb==md))||((ma==mc)&&(mb<md))){if(mc<=md)ans*=1;else ans=0;return;}
    	if((ma>mc)&&(mb<md)){if(mc==md)ans*=1;else ans=0;return;}
    }
    int main()
    {
    	scanf("%lld",&q);
    	get(sqrt(MAX));
    	while(q--){
    		ans=1;
    		scanf("%lld%lld%lld%lld",&a0,&a1,&b0,&b1);
    		for(int i=1;i<=m;++i)
    			work(p[i]);
    		if(a0>1) work(a0);
    		else if(b1>1&&b1!=a0)
    			work(b1);
    		printf("%lld
    ",ans);
    	}
    	return 0;
    }
    

    总结

    对于以上两种算法,一点个人理解:暴力算法采用某种自顶向下的判断性求解模式,即判断当前枚举到的(x)是否符合条件。更优的算法采用一种类似构造解的模式,根据解的特征自底向上地构造解,应该说是一种较难掌握的思维方式。写完这道题,我想朴素与优雅的算法的区别应该就在这里吧。

  • 相关阅读:
    RMQ(模板 ST 区间最值,频繁的间隔时间)
    编程算法基地-2.1利用字符串API
    android学习记录(三)百度地图错误---只有一个电话显示帧,没有地图内容。
    【MongoDB】在windows平台mongodb切片集群(三)
    Android MenuItem 设置文本颜色-TextColor设置
    wikioi 1034 家 实时动态的网络流量(费用流)
    where can I find source of com.android.internal.R.styleable.AlertDialog_multiChoiceItemLayout?
    Android开发之自定义Spinner样式的效果实现(源代码实现)
    Java注释@interface的用法
    依赖注入及AOP简述(十三)——AOP应用举例(完结) .
  • 原文地址:https://www.cnblogs.com/DarkValkyrie/p/11477719.html
Copyright © 2020-2023  润新知