1. 程式人生 > >兩個數的生成範圍(兩個生成元)(拓展歐幾里得演算法)

兩個數的生成範圍(兩個生成元)(拓展歐幾里得演算法)

最近遇到一個題,就是給兩個數,這兩個數有無限個,問你由這些數能得到哪些數。

還可以擴充套件成有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,終於又沒看題解完完全全做出一道題。

下面開始分析:

Come on,let's do it !

首先呢,我們得知道x和y的通解公式,是 x =x0 + b/g*t , y =y0 - a/g*t  (其中t為整數,x0,y0是方程的一組解,也就是呼叫exgcd函式得到的x,y),不知道的自己記到小本本上吧,沒什麼好講的。

①那麼x,y的值 和 最小步數有什麼關係呢?

我們舉幾個例子慢慢發覺關係

當x = 3時,y = 4 時,表示 走3步a,走4步b,就能到達終點了。

但是既然可以一步走a+b,為啥要重複走a,b呢,所以我們選擇走3步a+b,1步b。

再如,x = -3,y=-4時,同樣,我們走4步就可以了,之前的3可以合併

(注意,x和y是可以為負的)

那麼我們可以初步斷定,最小步數就是 x和y的最大值,或者絕對值的最大值(因為有可能為負)

但是x 為正,y為負,那咋搞

其實是x-y啦。

我想說的意思是,我們應該考慮x和y的正負號問題,我沒有那麼好的總結能力,不能一步覆蓋四種情況,只好分情況列舉,感覺也不麻煩。

但是又有問題

②通解那麼多,我們不可能列舉

我們再來看看

x 是關於t的一個方程,我們可以用直線表示出來,x與t是成正相關關係的,所以我們畫一個圖,順便再把y與t的關係畫出來

好了,有了上面的圖,我們可以更直觀的看答案。

為什麼我畫的兩條直線斜率一定相反呢,因為y的通項公式中是 y0-k*t,所以兩條直線一定相交,一定是一個向上走,一個向下走。

那麼仔細看圖我們發現,在p點處的答案是最優的,兩個都是正的,而且兩者的最大值也很小。

那麼,是不是對於所以的圖,都是交點附近答案最優呢,原來真的是的。

在交點的附近,x和y幾乎是相同的,所以一步就能走很多,a+b,我們讓x和y重複的越多,總步數就越少。

至於為什麼,同學們可以多畫幾個圖,交點在不同象限。

那麼由於交點不一定是整數,所以我們列舉交點前一個點和後一個點,這樣就能覆蓋了。

#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

*/