序列的GCD的生成集

序列的GCD的生成集

一个序列的GCD的生成集为:

存在一个正整数集合 S S S,该集合的GCD生成集为:

S gcd ⁡ = { a ∣ a = gcd ⁡ ( x 1 , x 2 , … , x n ) ∣ n > 0 , x 1 , x 2 , … , x n ∈ S } S_{\gcd} = \{ a | a = \gcd(x_{1},x_{2},\ldots,x_{n}) | n >0, x_{1},x_{2},\ldots,x_{n} \in S\} Sgcd={aa=gcd(x1,x2,,xn)n>0,x1,x2,,xnS}

即在集合 S S S中任意非空的子集,其所有元素构成的最大公约数。

序列的GCD的生成集的大小

∣ S gcd ⁡ ∣ |S_{\gcd}| Sgcd的值。

LeetCode 5725

对于可以表示出的最大公约数 a a a来说,其生成 a a a的子序列中的元素一定都是 a a a的倍数,如果不是 a a a的倍数,那么最大公约数不可能是 a a a。第二,可以表示出的最大公约数 a a a,当且仅当 S S S中所有 a a a的倍数的子集的最大公约数是 a a a

必要性:

假设我们已经选取了 S S S中所有 a a a的倍数的子集中的一些元素,现在的最大公约数不是 a a a,再往集合中添加元素,一直到把所有的 S S S中所有 a a a的倍数的子集中的元素都添加进来,此时最大公约数不会增大,也不会小于 a a a。如果最大公约数大于 a a a,那么就不可能生成 a a a(反证:如果存在生成 a a a的序列,那么必定是 a a a倍数集合的子集,最后,最大公约数反而增大了,不可能),如果最大公约数正好等于 a a a,那就是 a a a

充分性:

如果不是 a a a,那么绝对不可能比 a a a小,只能比 a a a大,如果存在生成 a a a的序列,那么必定是 a a a倍数集合的子集,最后,最大公约数反而增大了,不可能。

设数组 g [ y ] g[y] g[y] 表示当前遍历过的所有数中,所有 y y y 的倍数的最大公约数。

对于数组中的每个数 x x x,我们枚举其所有的约数 y y y,并使用 x x x 更新 g [ y ] g[y] g[y] 即可。

最终遍历数组 g g g,只要有 y = g [ y ] y = g[y] y=g[y],那么答案增加 1 1 1

我们也可以直接枚举 y y y,然后枚举 y y y的倍数。

class Solution
{
public:
    int gcd(int a, int b)
    {
        return b == 0 ? a : gcd(b, a % b);
    }
    int countDifferentSubsequenceGCDs(vector<int> &nums)
    {
        unordered_set<int> st;
        int mx = 0;
        for (int i : nums)
        {
            st.insert(i);
            mx = max(mx, i);
        }
        int cnt = 0;
        for (int y = 1; y <= mx; y++)
        {
            int g = 0;
            for (int k = y; k <= mx; k += y)
            {
                if (st.count(k))
                {
                    if (g == 0)
                        g = k;
                    else
                        g = gcd(g, k);
                }
                if(g == y)
                {
                    cnt++;
                    break;
                }
            }
        }
        return cnt;
    }
};

例题

AtCoder Beginner Contest 191 Problem F

我们发现,黑板上最终的数字必须小于等于黑板上最小的数字,而且黑板上最终的数字是黑板上某些元素的GCD。因此问题就转化成,求生成集中,小于等黑板上最小的数字的个数。

但是有一点不同,我们发现这道题元素个数少,元素值大,我们就不能枚举GCD了,我们只好选择求元素的约数了。

#include <bits/stdc++.h>

using namespace std;

#define FR freopen("in.txt", "r", stdin)
#define FW freopen("out.txt", "w", stdout)

typedef long long ll;

ll gcd(ll a, ll b)
{
    return b == 0 ? a : gcd(b, a % b);
}

ll arr[2005];

int main()
{
    int n;
    scanf("%d", &n);
    ll mx = 0, mi = LONG_LONG_MAX;
    for (int i = 0; i < n; i++)
    {
        ll val;
        scanf("%lld", &val);
        mx = max(mx, val);
        mi = min(mi, val);
        arr[i] = val;
    }

    unordered_map<ll, ll> gcdmp;

    for (int i = 0; i < n; i++)
    {
        ll tag = arr[i];
        for (ll c = 1; c * c <= tag; c++)
        {
            if (tag % c == 0)
            {
                if (!gcdmp.count(c))
                {
                    gcdmp[c] = tag;
                }
                else
                {
                    gcdmp[c] = gcd(gcdmp[c], tag);
                }
                ll o = tag / c;
                if (!gcdmp.count(o))
                {
                    gcdmp[o] = tag;
                }
                else
                {
                    gcdmp[o] = gcd(gcdmp[o], tag);
                }
            }
        }
    }
    int ans = 0;
    for (pair<ll, ll> p : gcdmp)
    {
        if (p.first == p.second && p.first <= mi)
        {
            ans++;
        }
    }

    printf("%d", ans);

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值