两个数的生成范围(两个生成元)(拓展欧几里得算法)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_39627843/article/details/82956618

最近遇到一个题,就是给两个数,这两个数有无限个,问你由这些数能得到哪些数。

还可以扩展成有n个数,问你能得到哪些数

这里其实是有一个结论的,就是:

①两个数互质,就可以生成很多很多数,而且从某个数开始就是连续的

②两个数不互质,生成的数一定是gcd(a,b)的倍数,gcd(a,b)就表示两个数的最小公倍数。

那么显然如果不互质,产生的数肯定没第一种情况多的。

关于更多的结论,下面会慢慢列举。

那么会遇到些什么样的题目呢?

我们看一下

下面是一道蓝桥杯的题,但是感觉出的还不错。

小明几乎每天早晨都会在一家包子铺吃早餐。他发现这家包子铺有N种蒸笼,其中第i种蒸笼恰好能放Ai个包子。每种蒸笼都有非常多笼,可以认为是无限笼。

每当有顾客想买X个包子,卖包子的大叔就会迅速选出若干笼包子来,使得这若干笼中恰好一共有X个包子。比如一共有3种蒸笼,分别能放3、4和5个包子。当顾客想买11个包子时,大叔就会选2笼3个的再加1笼5个的(也可能选出1笼3个的再加2笼4个的)。

当然有时包子大叔无论如何也凑不出顾客想买的数量。比如一共有3种蒸笼,分别能放4、5和6个包子。而顾客想买7个包子时,大叔就凑不出来了。

小明想知道一共有多少种数目是包子大叔凑不出来的。
输入
----
第一行包含一个整数N。(1 <= N <= 100)
以下N行每行包含一个整数Ai。(1 <= Ai <= 100)  

输出
----
一个整数代表答案。如果凑不出的数目有无限多个,输出INF。

例如,
输入:
2  
4  
5   

程序应该输出:
6  

再例如,
输入:
2  
4  
6    

程序应该输出:
INF

样例解释:
对于样例1,凑不出的数目包括:1, 2, 3, 6, 7, 11。  
对于样例2,所有奇数都凑不出来,所以有无限多个

我们看一下题解:

做法:这是扩展欧几里德变形的,有个定理。
如果满足所有数的最大公约数不为1则有无穷个,
否则都是有限个。然后利用完全背包就可以统计了。

#include <algorithm>
#include <string.h>
#include <iostream>
#include <stdio.h>
#include <string>
#include <vector>
#include <queue>
#include <map>
#include <set>
using namespace std;
int gcd(int a,int b){
    if(b == 0) return a;
    return gcd(b,a%b);
}
int arr[110],n;
const int N = 10010;
bool bk[N];
int main()
{
    scanf("%d",&n);
    for(int i = 0 ; i < n ; i ++)
        scanf("%d",&arr[i]);
    int g = arr[0];
    for(int i = 1 ; i < n ; i ++)
        g = gcd(g,arr[i]);
    if(g != 1)
    {
        printf("INF\n");
    }else{
        bk[0] = true;
        for(int i = 0 ; i < n ; i ++)
        {
            for(int j = 0 ; j + arr[i] < N ; j ++)
                if(bk[j])bk[j+arr[i]]= true;
        }
        int count = 0;
        for(int i = N-1 ; i >= 0 ; i --){
            if(bk[i] == false) count++;
        }
        printf("%d\n",count);
    }
    return 0;
}

对于代码里面完全背包的那里,我想解释一下,

n就是表示蒸笼的种类数,N是最大体积,也就相当于有1-n号物品,它们的体积不一样,有无限个,问你能凑出哪些体积,

那么我们就两层for循环就能解决问题了。

顺便说一下,通过上面的背包解法,我发现了个小规律,两个数互质的情况下,最后一个凑不出的数为 a*b-(a+b)

我们看看截图:

3 和5最后一个凑不出来的数为7, 从7后面的数都可以凑出来,而  7=3*5-(3+5)

我们再看一个

4和7最后一个凑不出来的数为 17 而 17=(4*7)-(4+7)

但是,其实这个小结论还没用过,嘻嘻,所以也不用记了,到时候打表找找规律吧。

然后,做到后来,才发现此类体型是拓展欧几里得的题目

先看看拓展欧几里得的模板

ll x,y;
ll exGcd(int a,int b)
{
    if(b==0)
    {
        x = 1;
        y = 0;
        return a;
    }
    ll ans = exGcd(b,a%b);
    ll t = x; x = y;
    y = t-a/b*y;
    return ans;
}

那么这个函数是干啥的呢,他是用来求不定方程的解的

exGcd这个函数范围的是a和b的gcd,但是x,和y才是最重要的,x,y是全局变量,表示方程ax+by=gcd(a,b)的解

那么有人可能会问,只能解这一个方程,用它干嘛,其实并不是,我们通过两边同乘以某个数,就可以得到任意方程的解

唯一的限制条件是:所有的数必须是整数。

那么我们来看看:

exgcd 解不定方程

  对于 ax+by=c 的不定方程,设 r=gcd(a,b)

  当 c%r!=0 时无整数解

  当 c%r=0 时,将方程右边 *r/c 后转换为 ax+by=r 的形式

  可以根据扩展欧几里得算法求得一组整数解 x0 , y0

  而这只是转换后的方程的解,原方程的一组解应再 *c/r 转变回去

  (如 2x+4y=4 转换为 2x+4y=2 后应再将解得的 x , y 乘上2)

  则原方程解为 x1=x0*c/r , y1=x0*c/r (乘以c/r)

  通解 x=x1+b/r*t , y=y1-a/r*t ,其中 t 为整数

注意,y的通解是减号---

那么我们就直接看题:

ZOJ 3593

There is an interesting and simple one person game. Suppose there is a number axis under your feet. You are at point A at first and your aim is point B. There are 6 kinds of operations you can perform in one step. That is to go left or right by a,b and c, here c always equals to a+b.

You must arrive B as soon as possible. Please calculate the minimum number of steps.

Input

There are multiple test cases. The first line of input is an integer T(0 < T ≤ 1000) indicates the number of test cases. Then T test cases follow. Each test case is represented by a line containing four integers 4 integers ABa and b, separated by spaces. (-231 ≤ AB < 231, 0 < ab < 231)

Output

For each test case, output the minimum number of steps. If it's impossible to reach point B, output "-1" instead.

Sample Input

2
0 1 1 2
0 1 2 4

Sample Output

1
-1

题意:

你要从一个起点走到一个终点,每一步你有三种走法,走a步,走b步,走a+b步,可以往左也可往右。

可以想象成是一个一维的坐标系,求最小的步数到终点,如果不能到,输出-1。

那么经过一番思考,终于发现,只要解一个方程就行了。

a*x+b*y=c

其中c=B-A,也就是,c为终点和起点的距离

那么解这个方程固然好解,直接套拓展欧几里得模板,但是和最小步数的关系,又有一番推导了。

当时WA的我差点看了题解,不过还好,在绝望之际AC,终于又没看题解完完全全做出一道题。

#include<cstdio>
#include<iostream>
#include<cstdlib>
#include<queue>
#include<cstring>
#include<algorithm>
#include<set>
#include<string>
#include<map>
#include<cmath>
#include<vector>
#include<time.h>
#define fori(l,r) for( int i = l ; i <= r ; i++ )
#define forj(l,r) for( int j = l ; j <= r ; j++ )
#define fork(l,r) for( int k = l ; k <= r ; k++ )
#define mem(a,val) memset(a,val,sizeof a)
#define lef rt<<1
#define rig rt<<1|1
#define mid (l+r)>>1
#define inf 0x3f3f3f3f
#define llinf 0x3f3f3f3f3f3f3f3f
using namespace std;
typedef long long ll;
const int maxn = 1e4+4;
ll x,y;
ll exgcd( ll a,ll b )
{
    if( b == 0 )
    {
        x = 1;
        y = 0;
        return a;   //返回gcd(a,b)
    }
    ll ans = exgcd(b,a%b);
    ll t = x;   x = y;
    y = t-a/b*y;
    return ans;
}
int main()
{
    ll A,B,a,b,c,g;
    int T;
    scanf("%d",&T);
    while( T-- )
    {
        cin>>A>>B>>a>>b;
        c = B-A;
        g = exgcd(a,b);

        if( c%g != 0 )
        {
            puts("-1");
            continue;
        }
        x = c/g*x,y = c/g*y;
        ll t = (y-x)*g/(a+b);
        ll ans = llinf;
        for( ll i = t-1 ; i <= t+1 ; i++ )
        {
            ll xx = x+b/g*i;
            ll yy = y-a/g*i;
            if( xx >= 0 && yy >= 0 )
                ans = min(ans,max(xx,yy) );
            else if( xx >= 0 && yy < 0 )
                ans = min(ans,xx-yy);
            else if( xx < 0 && yy >= 0 )
                ans = min(ans,yy-xx);
            else if( xx < 0 && yy < 0 )
                ans = min(ans,max(abs(xx),abs(yy)) );
        }
        cout<<ans<<endl;
    }
    return 0;
}

/*
//freopen("E:\\problem list\\codeblocks\\A\\input.txt","r",stdin);
//freopen("E:\\problem list\\codeblocks\\A\\standardoutput.txt","w",stdout);

12312

0 7 1 2

*/

猜你喜欢

转载自blog.csdn.net/qq_39627843/article/details/82956618