算法提升之数学-(裴蜀定理)

今天分享的是裴蜀定理,通过学习裴蜀定理,我们可以解决有关整数表达的相关问题。

1.裴蜀定理的定义

2.裴蜀定理的拓展

问题一

题目描述

小明几乎每天早晨都会在一家包子铺吃早餐。他发现这家包子铺有 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

代码部分:

#include <bits/stdc++.h>
using namespace std;
const int Max=1e5;
int ans;
bool dp[Max];
int a[105];
int main()
{
  int n;cin>>n;
   int g;
  for(int i=1;i<=n;i++){
     cin>>a[i];
     if(i==1)g=a[i];
     g=__gcd(g,a[i]);
}
if(g!=1){
  cout<<"INF"<<'\n';
  return 0;
}
dp[0]=1;
for(int i=1;i<=n;i++){
  for(int j=a[i];j<Max;j++){
    dp[j]=dp[j]|dp[j-a[i]];
  }
}
for(int i=0;i<Max;i++){
  if(!dp[i])ans++;
}
cout<<ans<<endl;
  return 0;
}

这道题是关于裴蜀定理比较难的应用了,希望大家可以好好理解。

结合裴蜀定理的推论:

  1. 若所有Ai的最大公因数g!=1:

    • 能凑出的 X 必须是 g 的倍数(因为 Ai 都是 g 的倍数,它们的组合也必然是 g 的倍数)。
    • 所有不是 g 的倍数的正整数),都无法被凑出。
    • 这样的数有无限多个(因为非 g 倍数的数在正整数中有无穷多个)。
  2. 若所有 Ai 的最大公因数 g! = 1:

    • 根据数论中的 “硬币问题” 结论:当 \(g=1\) 时,存在一个最大的无法凑出的数(称为 “最大不可表数”),且大于这个数的所有数都能被凑出。因此,无法凑出的数是有限个

同时通过排序只要求最小两个笼子的不可表达数即可,同时与背包问题相结合。

第二题:

问题描述

小蓝有一个计算器,计算器的初始值为 0 ,小蓝可以对其进行任意次操作,每次操作小蓝可以把计算器的值加上 a1​,a2​,...,an​ 中的任意一个整数。现在小蓝想知道进行任意次操作后计算器的值对 m取模后有几种可能。但是小蓝不擅长数学问题,请你帮她解决这个问题。

输入格式

第一行输入两个整数,代表 n,m 。 第二行输入 n 个整数,代表a1​,a2​,a3​,...,an​ 。

输出格式

输出一行一个整数,代表最终对 m 取模的结果的种类数。

输入案例:

3 3
1 2 3

输出案例:

3

代码部分:

#include<bits/stdc++.h>
#define ll long long
#define inf 4e18
#define IOS ios::sync_with_stdio(false) , cin.tie(nullptr)
using namespace std ;
const int N = 2e6 + 5, MOD = 998244353 ;

int main( ){
    IOS ; 
    int n , m ; cin >> n >> m ; 
    int ans = m ; 
    for( int i = 1 ; i <= n ; ++i ){
        int x ; cin >> x ; 
        ans = __gcd( ans , x ) ; 
    } 
    cout << m/ans << "\n" ; 
    return 0 ; 
}

解题思路

这里我们首先介绍一下裴蜀定理:设 a,b 是不全为 0 的整数,那么存在整数 x,y 满足 ax+by=gcd(a,b)。

针对这个题,我们可以根据题目得到等式 v=a1x1+a2x2+…+anxn​,其中 x1​,x2​,x3​,…,xn​ 都是大于等于 0 的整数。我们可以观察到这个与 v 相关的方程其实是裴蜀定理的一个推论。如果存在不全为零的整数 x1​,x2​,x3​,…,xn​,使得方程v=a1​x1​+a2​x2​+…+an​xn​ 有解,那么一定存在 v 能整除 a1,a2,a3,…,ana1​,a2​,a3​,…,an​ 的最大公约数,即 v=t×gcd(a1​,a2​,a3​,…,an​),这里 tt 是大于等于 1 的整数。

接下来,我们令p=vmodm 和 d=gcd(a1​,a2​,a3​,…,an​),其中 p 是小于等于 m 的非负整数。通过将方程变形,我们可以得到 d×t−m×n=p,其中 t 和 n 都是大于等于 0的整数。最终,p 的种类数就是我们的答案。

根据裴蜀定理,我们知道 p 必须能整除 d 和 m,且 p 是小于 m 的非负整数。因此,p的种类数即为 mgcd(d,m)g​。

因此,我们的解题步骤是枚举数组 a 的每一个元素,然后计算 d=gcd(m,a1,a2,a3,…,an)。最终的答案即为 dm​。

第三题:

问题描述

给定正整数 n,x和一个长度为 n 的正整数数列 a1​,a2​,…an​。

你可以执行如下两种操作任意多次:

操作一:选择一个 ai​,使x=x+ai​。

操作二:选择一个 ai,如果 x−ai≥0;使 x=x−ai​,否则 x 不变。

求操作后 xx 最小是多少 。

输入格式

输入第一行,两个正整数 n,x。

输入第二行,n 个正整数 a1​,a2​…an​。

输出格式

输出仅一行,包含一个整数,表示答案。

输入案例:

3 7
2 6 8

输出案例:

1

代码部分:

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int N = 2e5 + 10;
int a[N];
int main()
{
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int n, x;
    cin >> n >> x;
    for (int i = 1; i <= n; ++i)
        cin >> a[i];
    int d = a[1];
    for (int i = 2; i <= n; ++i)
        d = __gcd(d, a[i]);
    cout << x % d;
}

解题思路

一点点思维加裴蜀定理。

设 M 是一个足够大的正整数,那么我们先执行 M 次操作一并选择 a1​,即x=x+a1​×M。

因为 M 足够大,所以之后任意时候执行操作二时,都不会出现 x−ai​<0。

最后再执行 M 次操作二并选择 a1​ 将 x 变回来。

由此我们可以认为在操作二中的0x−ai​>0 的限制是可忽略的。

于是此题即为:让 x 任意加减 ai​,使最终的 x 满足 x≥0 的同时,x 最小。

即让 x−(x1a1+x2a2+⋯+xnan)满足大于零的同时最小。

根据裴蜀定理:x1​a1​+x2​a2​+⋯+xn​an​ 的值域为 {y=k×gcd(a1​,a2​…an​)∣k∈Z},所以 x 最小为 xmodgcd(a1​,a2​…an​)。

复杂度 O(n)。

好了今天的分享就到这里,希望大家多多关注。