Codeforces Round #505 (div1+div2) B 【数学】

版权声明:如需转载,记得标识出处 https://blog.csdn.net/godleaf/article/details/81871747

题目链接:http://codeforces.com/contest/1025/problem/B

题意:

给出n个数对,一个数对有两个数字,输出一个值,这个值能整除每个数对中任意一个数字,并输出这个值,多个解输出一个就行,这个值如果是1,那么就输出-1;

思路:(我自己平时最怕的就是数学题,一些整除或者倍数关系什么的,可能会表述错误,但是代码是能AC的,要是文字说明看不懂的话就直接看代码吧)

先看看暴力的方法:
因为要求的这个数字能够被每一个数对中任意一个数字整除,也就意味着,这个数字是每一个数对中任意一个数的倍数;
求出任意一个数对的所有的倍数,我们需要输出的值就一定在这些倍数里面;所以,暴力枚举的方法是先求出第一个数对中所有的倍数,并放在一个数组里面,然后依次遍历数组,把数组中那个能被所有数对中任意一个数整除的值输出就行了;这个方法的时间复杂度比较难计算,因为我们很难计算出一个数的所有倍数的个数;所以要尽可能的剪一点枝;我们假设一个数对有一个数是18,那么相应的倍数就是 2,3,6,9,18(如果倍数是1就直接输出-1,所以这里算倍数的时候不算1),我们假设6能够被所有数对中任意一个数整除,那么2,3(2*3 = 6)是不是也一定能够被数对中的任意一个数整除;所以算倍数的时候,倍数之间存在倍数关系就应该删去大的那个,减少要遍历倍数的次数;所以在减去倍数之间成倍数关系并且是较大的那个之后,18的倍数就应该是2和3;当要求倍数的数字非常大的时候,这个剪枝就能省下很多时间;这个剪枝要如何实现?这里有一个方法,但是我不会证明个方法的可行性,暂且当理论用吧;这是我们平时求一个数的倍数,效率比较高的方法 for(int i = 2; i*i <= x; ++i) 
我们只需要在这个方法上面加上一句代码就行了,具体看下下面的代码;

​
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<cmath>
#include<queue>
#include<map>
#include<stack>
#include<sstream>
#include<vector>
#include<string>
#include<set>

using namespace std;

#define IOS ios::sync_with_stdio(false); cin.tie(0);

int read(){
    int r=0,f=1;char p=getchar();
    while(p>'9'||p<'0'){if(p=='-')f=-1;p=getchar();}
    while(p>='0'&&p<='9'){r=r*10+p-48;p=getchar();}return r*f;
}

typedef long long ll;
const int Maxn = 2e5;
const long long LINF = 1e18;
const int INF = 0x3f3f3f3f;


int x[Maxn],y[Maxn],r[10005],M = 0;

void solve (int x) {
    for (int i = 2; i*i <= x; ++i) {
        if(x%i == 0) {
                r[M++] = i;
               while (x%i == 0) x/=i; // 这段代码能够跳过倍数之间存在倍数关系且较大的那个数
        }
    }
    if(x > 1) r[M++] = x;
}

int main (void)
{
   // IOS;
    int n;
    scanf("%d",&n);
    for (int i = 0; i < n; ++i) {
        scanf("%d%d",&x[i],&y[i]);
    }
    if(n == 1) { 
            if(x[0] != 1) printf("%d",x[0]);
            else if(y[0] != 1) printf("%d",y[0]);
            else printf("-1");
            return 0;
    }
    solve (x[0]); solve (y[0]);
    // 这么这两句能够把数组中重复的数字去掉,是标准函数库的函数
    sort(r,r+M);
    M = unique(r,r+M)-r;

    for (int i = 0; i < M; ++i) { 
        int j = 1;
        cout << r[i] << endl;
        for (; j < n; ++j) {
            if(x[j]%r[i] && y[j]%r[i]) break;
        }
        if(j == n) {
            printf("%d",r[i]);
            return 0;
        }
    }
    printf("-1");
    return 0;
}

​

第二种方法:

因为要求的值是一个数对中任意一个数的倍数,那么这两个数对相乘得到的数一定能被我们要求的数整除;所以,把两个数对相乘,合成一个数,求出所有相乘后的数的公约数,这里可以用gcd来求最大公约数,得到的最大公约数如果是 1,就说明不存在一个数能满足题目的条件,如果大于1,要尽可能的把这个公约数化简到最小,因为得到的最大公约数是一个数对相乘得到的结果,所以这个公约数可能会大于一些数对的数,化简的方法就是和所有给出的数对进行gcd的操作;注意: __gcd() 不是标准库的函数;

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<cmath>
#include<queue>
#include<map>
#include<stack>
#include<sstream>
#include<vector>
#include<string>
#include<set>

using namespace std;

#define IOS ios::sync_with_stdio(false); cin.tie(0);
#define gcd std::__gcd

int read(){
    int r=0,f=1;char p=getchar();
    while(p>'9'||p<'0'){if(p=='-')f=-1;p=getchar();}
    while(p>='0'&&p<='9'){r=r*10+p-48;p=getchar();}return r*f;
}

typedef long long ll;
const int Maxn = 2e5;
const long long LINF = 1e18;
const int INF = 0x3f3f3f3f;

ll x[Maxn],y[Maxn];

int main (void)
{
    // IOS;
    ll n,ans = 0;
    cin >> n;
    for (int i = 0; i < n; ++i) {
        cin >> x[i] >> y[i];
        ans = gcd(ans,x[i]*y[i]);
    }
    if(ans == 1) return puts("-1"),0;
    for (int i = 0; i < n; ++i) {
        if(gcd(ans,x[i]) > 1) ans = gcd(ans,x[i]);
        else ans = gcd(ans,y[i]);
    }
    cout << ans << endl;
    return 0;
}

猜你喜欢

转载自blog.csdn.net/godleaf/article/details/81871747