今天分享的是裴蜀定理,通过学习裴蜀定理,我们可以解决有关整数表达的相关问题。
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;
}
这道题是关于裴蜀定理比较难的应用了,希望大家可以好好理解。
结合裴蜀定理的推论:
-
若所有Ai的最大公因数g!=1:
- 能凑出的 X 必须是 g 的倍数(因为 Ai 都是 g 的倍数,它们的组合也必然是 g 的倍数)。
- 所有不是 g 的倍数的正整数),都无法被凑出。
- 这样的数有无限多个(因为非 g 倍数的数在正整数中有无穷多个)。
-
若所有 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=a1x1+a2x2+…+anxn 有解,那么一定存在 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)满足大于零的同时最小。
根据裴蜀定理:x1a1+x2a2+⋯+xnan 的值域为 {y=k×gcd(a1,a2…an)∣k∈Z},所以 x 最小为 xmodgcd(a1,a2…an)。
复杂度 O(n)。
好了今天的分享就到这里,希望大家多多关注。