关于exgcd的总结
我们主要讨论的是
1.exgcd算法
1.1 关于解的存在性
有裴蜀定理知,对于方程
tips:裴蜀定理 如果
1.2exgcd算法介绍
要求
当我们求出
具体处理方法就是:
然后对比两边得到:
注意边界条件:
int exgcd(int a,int b,int &x,int &y) { if(b==0) { x = 1,y = 0; return a; } int d = exgcd(b,a%b,y,x); y -= (a/b)*x; return d; }
1.3解的通式
对于
正确性也很显然,我们感性的去理解一下,因为要保证原式的值不变,那么
1.4关于 的解
我们知道,对于
方程是否有正整数解?
我们发现,
-
当
同号时: 负相关,即 越大 越小。当求出的 是最小的时候,有 是最大的。 -
当
异号时, 正相关,即同增同减。
所以,一个合理的想法,我们对
那么如何求解最小正整数解?
由通解可知:
(*)
通过
ll exgcd(ll a, ll b, ll &x, ll &y) { if(b == 0) { x = 1; y = 0; return a; } ll xx,yy; ll d = exgcd(b, a % b, xx, yy); x = yy; y = xx - (a / b) * yy; return d; } int main() { int _;cin>>_; while(_--) { ll a, b, x, y, m; scanf("%lld%lld%lld", &a, &b, &m); ll d = exgcd(a, b, x, y); if(m % d != 0) { puts("-1"); continue; } a /= d; b /= d; m /= d; ll xx = (ll) x * (m % b) % b; if(xx < 0) xx = xx + b; ll yy = (ll)(m - a * xx) / b; if(yy < 0) puts("-1"); else printf("%lld %lld\n", xx, yy); } }
关于正整数解的个数?
根据通式算出上下界即可。
因为是正整数解那么:
于是
又因为是整数,那么取整一下。同理另一边也是一样的道理。
exgcd求出的特解的范围?
对于
在数值上有:
对于
关于正解和非负解的几个结论:
由于
1.非负整数解
设
- 当
时,有非负解,且解的个数为 或 - 当
时,没有非负解。 - 当
时,恰有 个正整数有非负解,具体是哪些不清楚。
2.正整数解
正整数解的个数为:
- 当
时有正解 - 当
时无解 - 当
时,恰有 个正整数有非负解,具体是哪些不清楚。
推论:
- 设
且 时, 是不能表示成 形式的最大整数。 - 设
且 时, 是不能表示成 形式的最大整数。
显然如果
有解的充要条件是方程 有非负解。
// AC one more times // nndbk #include <bits/stdc++.h> using namespace std; typedef long long ll; const int mod = 1e9 + 7; const int N = 2e5 + 10; ll exgcd(ll a,ll b,ll &x,ll &y) { if(b==0) { x = 1,y = 0; return a; } ll d = exgcd(b,a%b,y,x); y -= (a/b)*x; return d; } int main() { ios::sync_with_stdio(false); cin.tie(nullptr), cout.tie(nullptr); int t; cin>>t; while(t--) { ll a,b,c,x,y; cin>>a>>b>>c; ll d = exgcd(a,b,x,y); if(c%d) cout<<-1<<"\n"; else{ a/=d,b/=d,c/=d; x*=c,y*=c;//特解 x = (x%b+b)%b; x = x==0?b:x; y = (c-a*x)/b; if(y>0) { ll xmin,xmax,ymin,ymax; xmin = x,ymax = y; y %= a; y = y==0?a:y; x = (c-b*y)/a; xmax = x,ymin = y; ll cnt = (xmax-xmin)/b+1;//每隔b一个整数解 cout<<cnt<<" "<<xmin<<" "<<ymin<<" "<<xmax<<" "<<ymax<<"\n"; } else//有整数解,但无正整数解 { ll xmin,ymin; xmin = x; y = y%a+a; ymin = y; cout<<xmin<<" "<<ymin<<"\n"; } } } return 0; }
1.5*取模意义下的一元二次方程的最小值
起源是关于这个题中提取的思路。
考虑以下两个问题:
的最小值 的最小值
先说结论:
的最小值是: 的最小值是
①先讨论第一个:
那么我们只要考虑
设
tips:同余 设
显然上面这个柿子已经没有模的意义了,那么上面这个
那么
②那么对于
上面这个柿子的含义就是要保证
有了上面的分析,下面来说这个题就很容易了。
题目是要求:
// AC one more times // nndbk #include <bits/stdc++.h> using namespace std; typedef long long ll; const int mod = 1e9 + 7; const int N = 2e5 + 10; ll n,m; ll exgcd(ll a, ll b, ll &x, ll &y) { if(b == 0) { x = 1; y = 0; return a; } ll xx,yy; ll d = exgcd(b, a % b, xx, yy); x = yy; y = xx - (a / b) * yy; return d; } int main() { ios::sync_with_stdio(false); cin.tie(nullptr), cout.tie(nullptr); cin>>n>>m; ll sum = 0; for(int i = 1; i <= n; i++) { ll x; cin>>x,sum += x; } ll a = n,b = (n+1ll)*n/2,x,y; //ax+by=k1g1(mod m) ll g1 = exgcd(a,b,x,y); //k1g1+tm = k2g2 ll k1,t; ll g2 = exgcd(g1,m,k1,t); //ans = min(sum + k2g2) ll ans = sum%g2; k1 *= ((ans-sum)/g2)%m; k1 %= m; x*=k1,y*=k1; cout<<ans<<"\n"; cout<<(x%m+m)%m<<" "<<(y%m+m)%m<<"\n"; return 0; }
1.6 一些习题练习
1.I. Step
思路:由题意转化得:我们要求出
设
设
即:
问题转化为求:
显然
// AC one more times // nndbk #include <bits/stdc++.h> using namespace std; typedef long long ll; const int mod = 1e9 + 7; const int N = 2e5 + 10; ll lcm(ll a,ll b) { return a/__gcd(a,b)*b; } vector<ll>a; map<ll,ll>mp; void primer(ll x) { for (ll i = 2; i <= x / i; i++) { if (x % i == 0) { int s = 0; a.push_back(i); mp[i] = 1; while (x % i == 0) { x = x / i; s++; mp[i]*=i; } } } if (x > 1)a.push_back(x),mp[x] = x;; } ll exgcd(ll a, ll b, ll &x, ll &y) { if(b == 0) { x = 1; y = 0; return a; } ll xx,yy; ll d = exgcd(b, a % b, xx, yy); x = yy; y = xx - (a / b) * yy; return d; } ll p[N]; int main() { ios::sync_with_stdio(false); cin.tie(nullptr), cout.tie(nullptr); int n; cin>>n; for(int i = 1;i <= n; i++) cin>>p[i]; ll LCM = p[1]; for(int i = 1;i <= n; i++) LCM = lcm(LCM,p[i]); ll t = LCM*2; primer(t); int sz = a.size(); ll ans = 1e18; for(int i = 0;i < (1<<sz); i++) { ll A = 1,B,x,y; for(int j = 0;j < sz; j++) { if((i>>j)&1)A*=mp[a[j]]; } B = t/A; ll d = exgcd(A,B,x,y); //ax+1=by //-ax+by = 1 x = -x; x = (x%(B/d)+(B/d))%(B/d); if(A*x!=0) { ans = min(ans,A*x); } } cout<<ans<<"\n"; return 0; }
2.A. Modulo Ruins the Legend
思路:什么讲解exgcd的时候又讲,主要思想就是,先去除取模的意义。什么意思呢?
就是说对于
即
// AC one more times // nndbk #include <bits/stdc++.h> using namespace std; typedef long long ll; const int mod = 1e9 + 7; const int N = 2e5 + 10; ll n,m,sum; ll exgcd(ll a, ll b, ll &x, ll &y) { if(b == 0) { x = 1; y = 0; return a; } ll xx,yy; ll d = exgcd(b, a % b, xx, yy); x = yy; y = xx - (a / b) * yy; return d; } int main() { ios::sync_with_stdio(false); cin.tie(nullptr), cout.tie(nullptr); cin>>n>>m; for(int i = 1;i <= n; i++) { ll x; cin>>x,sum += x; } ll a = n,b = (n+1)*n/2,x,y,k1,t; ll g1 = exgcd(a,b,x,y); ll g2 = exgcd(g1,m,k1,t); //ax+by = k1g1 (mod m) //k1g1+tm = k2g2 //ans = min(sum + k2g2) = sum%g2 //k2 = ((ans-sum)/g2)%m //k1*=k2 //x*=k1,y*=k1; ll ans = sum%g2; cout<<ans<<"\n"; ll k2 = (ans-sum)/g2%m; k1*=k2,k1%=m; x *= k1,y*=k1; cout<<(x%m+m)%m<<" "<<(y%m+m)%m<<"\n"; return 0; }
3.M-Water_“范式杯”2023牛客暑期多校训练营1
思路:不妨假设
显然的
因为不可能同时为负数,那么当
不妨假设
对于以下的讨论不妨假设
含义就是喝
对于
- 将
倒满 - 将
中水导入 ,此时杯中: 的水 - 将
的水喝掉 - (*)将第二个杯子里面的
倒掉(如果是最后一步就不要这一步)
那么
最后答案就是
接下来我们只需先求出一个特解,然后在特解附件去求它的通解就可以了。
// AC one more times // nndbk #include <bits/stdc++.h> using namespace std; typedef long long ll; const int mod = 1e9 + 7; const int N = 2e5 + 10; ll exgcd(ll a, ll b, ll &x, ll &y) { if(b == 0) { x = 1; y = 0; return a; } ll xx,yy; ll d = exgcd(b, a % b, xx, yy); x = yy; y = xx - (a / b) * yy; return d; } int main() { ios::sync_with_stdio(false); cin.tie(nullptr), cout.tie(nullptr); int t; cin>>t; while(t--) { ll A,B,X,x,y; cin>>A>>B>>X; ll d = exgcd(A,B,x,y); if(X%d){ cout<<"-1\n"; continue; } else{ //x = xxA+yyB //x = (xx+yy-yy)A+yyB //x = (xx+yy)A+(-yy)(A-B) //ans1 = 2*(xx+yy) //ans2 = 4*(-yy-1)+3 //ans1+ans2 = 2xx+2yy-4yy-4+3 = 2xx-2yy-1 A /= d; B /= d; X /= d; ll xx = (ll) (x % B) * (X % B) % B; ll yy = (ll)(X - A * xx) / B; ll ans = 1e18; for(int i = -10;i <= 10; i++) { ll nx = (xx + B*i),ny = (yy - A*i); if(nx>=0 && ny>=0) ans = min(ans,2*(nx+ny)); else ans = min(ans,2*abs(nx-ny)-1); } cout<<ans<<"\n"; } } return 0; }
4.Red-Black Pepper
思路:由于我们只能在一个店买,该商店红辣椒
第
我们设
我们先考虑一个简单的问题:买
让我们感性的去理解一下:假设我全买了黑的,再用贪心的策略把其中
全买黑的贡献
那么我们按照
我们观察
那么我们
那么处理好了
很显然
我们先求出来了
如果说
否则的话继续讨论。
特解知道了,我们可以进一步求出它们的通解:
回归问题
对于
因为
对于通解:
显然
那么对于通解的最小值就是
然后我们在从任意集中,选择离
// AC one more times // nndbk #include <bits/stdc++.h> using namespace std; typedef long long ll; const int mod = 1e9 + 7; const int N = 3e5 + 10; ll n,m,c[N],f[N],a[N],b[N]; bool cmp(int a,int b) { return a>b; } ll exgcd(ll a, ll b, ll &x, ll &y) { if(b == 0) { x = 1; y = 0; return a; } ll xx,yy; ll d = exgcd(b, a % b, xx, yy); x = yy; y = xx - (a / b) * yy; return d; } int main() { ios::sync_with_stdio(false); cin.tie(nullptr), cout.tie(nullptr); cin>>n; for(int i = 1;i <= n; i++) cin>>a[i]>>b[i],c[i] = a[i]-b[i],f[0]+=b[i]; sort(c+1,c+1+n,cmp); for(int i = 1;i <= n; i++)f[i] = f[i-1]+c[i]; ll high = 0,maxv = f[0]; for(int i = 1;i <= n; i++) if(f[i]>maxv)high = i,maxv = f[i]; cin>>m; while(m--) { ll a,b,x,y; cin>>a>>b; ll g = exgcd(a,b,x,y); if(n%g){ cout<<"-1\n"; continue; } x = x*(n/g)%(b/g); if(x<0)x+=(b/g); if(a*x>n){ cout<<"-1\n"; continue; } /* x = x0+b/g*t y = y0-a/g*t ax+by = n ax = ax0+ab/g*t by = ay0-ab/g*t ax = ax0+dt */ ll d = a/g*b;//lcm //ax+by = n //ax+t[a,b] = ax+td //ax+td = n //t = (n-ax)/d //t的最小值显然是0,最大值是(n-ax)/d ll mi = a*x,mx = n-(n-a*x)%d; ll ans = max(f[mi],f[mx]); if(high>=mi && high<=mx) { ll l = high-(high-mi)%d;//下取整 ll r = high+(mx-high)%d;//上取整 ans = max(ans,max(f[l],f[r])); } cout<<ans<<"\n"; } return 0; }
5.Two Arithmetic Progressions
题意:给定
思路:题目要求
我们移向得:
显然我们可以用
首先因为参数有负数,那么我们可以知道,
我们可以直接通过
那么通解就是:
又由于
我们令
那么我们的通解就变成了:
因为我们要求
那么可以取的
即:
接下来我们只需要求出
注意:取整要手写,因为当其是负数时候,c++默认往0取整,所以下取整我们要自己写
#include<bits/stdc++.h> using namespace std; typedef long long ll; ll exgcd(ll a, ll b, ll &x, ll &y) { if(b == 0) { x = 1; y = 0; return a; } ll xx,yy; ll d = exgcd(b, a % b, xx, yy); x = yy; y = xx - (a / b) * yy; return d; } ll floordiv(ll a,ll b) { //因为当其是负数时候,c++默认往0取整,所以下取整我们要自己写 if(a%b==0)return a/b; else if(a>0)return a/b; else return a/b-1; } ll ceildiv(ll a,ll b) { if(a%b==0)return a/b; else if(a>0)return a/b+1; else return a/b; } int main() { ll a1,b1,a2,b2,l,r,x,y; cin>>a1>>b1>>a2>>b2>>l>>r; ll d = exgcd(a1,a2,x,y); y = -y; if((b2-b1)%d){ cout<<"0\n"; return 0; } x *= (b2-b1)/d,y *= (b2-b1)/d; ll dx = abs(a2/d),dy = abs(a1/d); x = (x%dx+dx)%dx,y = (a1*x-(b2-b1))/a2; if(y<0)y = (y%dy+dy)%dy,x = (a2*y+(b2-b1))/a1; ll kmin = max(0ll,ceildiv(l-a1*x-b1,a1*dx)); ll kmax = floordiv(r-a1*x-b1,a1*dx); cout<<max(0ll,kmax-kmin+1)<<"\n"; return 0; }
6.Integers Have Friends
思路:这个题是关于
CF1216D Swords
题意:一共有
思路:很显然,因为初始每个种类数量是一样的,可以确定初始每个的数量
// AC one more times // nndbk #include <bits/stdc++.h> using namespace std; typedef long long ll; const int mod = 1e9 + 7; const int N = 2e5 + 10; ll a[N]; int main() { ios::sync_with_stdio(false); cin.tie(nullptr), cout.tie(nullptr); int n; cin>>n; ll maxv = 0; for(int i = 1;i <= n; i++) { cin>>a[i]; maxv = max(maxv,a[i]); } ll ans = 0; for(int i = 1;i <= n; i++) { a[i] = maxv-a[i]; } ll g = a[1]; for(int i = 1;i <= n; i++) g = __gcd(g,a[i]); for(int i = 1;i <= n; i++) ans += a[i]/g; cout<<ans<<" "<<g<<"\n"; return 0; }
Array Stabilization (GCD version)
题意:给定长度为
思路:考虑对于
看到这里,变化就很明显了。题目问的是最小操作次数,也很显然,这个操作次数满足单调性,因为
那么我们需要一个快速处理出区间
// AC one more timesk // nndbk #include <bits/stdc++.h> using namespace std; typedef long long ll; const int mod = 1e9 + 7; const int N = 2e5 + 10; const int LOGN = 20; int a[N],n; struct S_T { // op 函数需要支持两个可以重叠的区间进行合并 // 例如 min、 max、 gcd、 lcm 等 int f[22][N], lg[N]; void build(int n) { lg[0] = -1; for (int i = 1; i <= n; ++i) { f[0][i] = a[i]; lg[i] = lg[i / 2] + 1; } for (int i = 1; i <= 20; ++i) for (int j = 1; j + (1 << i) - 1 <= n; ++j) f[i][j] = __gcd(f[i - 1][j], f[i - 1][j + (1 << (i - 1))]); } int query(int l, int r) { if(l <= n && r > n)return __gcd(query(l,n),query(1,r-n)); int len = lg[r - l + 1]; return __gcd(f[len][l], f[len][r - (1 << len) + 1]); } } ST; bool judge(int k) { int t = ST.query(1,1+k-1); for(int i = 2;i <= n; i++) if(ST.query(i,i+k-1) != t)return false; return true; } int main() { ios::sync_with_stdio(false); cin.tie(nullptr), cout.tie(nullptr); int t; cin>>t; while(t--) { cin>>n; for(int i = 1;i <= n; i++) cin>>a[i]; ST.build(n); int l = 1,r = n; while(l <= r) { int mid = (l+r)>>1; if(judge(mid))r = mid-1; else l = mid+1; } cout<<r<<"\n"; } return 0; }
说完上面两个题,接下来来说这个题
题意:
- 给定
和一个长度为 的数组 ,求一个最长的区间 ,使得存在 和 ,对于所有 (即区间内所有数对 取模余数相等),输出最长区间长度(区间长度定义为 )。 - 有多组测试数据。
思路:令
那么就有:
对于任意两项:比如
即:
那结论就是:若
也就是说,任意两项差是
我们可以对原序列做差得到新数列,因为
我们考虑枚举
小细节:
- 多组数据,注意清空
- 特判
的情况 - 因为做差得的新数组只有
项,最大子区间长度应该+1才是答案。 - 开
// AC one more times // nndbk #include <bits/stdc++.h> using namespace std; typedef long long ll; const int mod = 1e9 + 7; const int N = 2e5 + 10; const int LOGN = 20; ll a[N],b[N],n; struct S_T { // op 函数需要支持两个可以重叠的区间进行合并 // 例如 min、 max、 gcd、 lcm 等 ll f[22][N], lg[N]; void build(int n) { lg[0] = -1; for (int i = 1; i <= n; ++i) { f[0][i] = a[i]; lg[i] = lg[i / 2] + 1; } for (int i = 1; i <= 20; ++i) for (int j = 1; j + (1 << i) - 1 <= n; ++j) f[i][j] = __gcd(f[i - 1][j], f[i - 1][j + (1 << (i - 1))]); } void clear(int n) { for (int i = 1; i <= 20; ++i) for (int j = 1; j + (1 << i) - 1 <= n; ++j) f[i][j] = 0; } ll query(int l, int r) { if(l <= n && r > n)return __gcd(query(l,n),query(1,r-n)); int len = lg[r - l + 1]; return __gcd(f[len][l], f[len][r - (1 << len) + 1]); } } ST; //a≡b(mod m) //m|(a-b) signed main() { ios::sync_with_stdio(false); cin.tie(nullptr), cout.tie(nullptr); int t; cin>>t; while(t--) { cin>>n; for(int i = 1;i <= n; i++) cin>>b[i]; for(int i = 1;i < n; i++) a[i] = abs(b[i]-b[i+1]); if(n==1){ cout<<1<<"\n"; continue; } n--; ST.clear(n); ST.build(n); int ans = 0; //枚举左端点二分右端点 for(int i = 1;i <= n; i++) { int l = i,r = n; if(a[i]==1)continue; while(l<=r) { int mid = (l+r)>>1; if(ST.query(i,mid)>1)l = mid+1; else r = mid-1; } ans = max(ans,l-1-i+1); } cout<<ans+1<<"\n"; } return 0; }
7.E. Identical Parity
思路:思维+
我们可以把题目看成一个长度为
对于下标为:
上的奇偶性要是一样的,即
我们考虑按上述方法把原序列按照下标分成
包含数字个数为
又由于整个序列上应该有
那么就考虑
含义就是:取
如何判断呢?考虑先用
那么通解就是
那么:
接下来看这两个不等式两边有没有交集,再确定区间里面有没有
// AC one more times // nndbk #include <bits/stdc++.h> using namespace std; typedef long long ll; const int mod = 1e9 + 7; const int N = 2e5 + 10; ll exgcd(ll a, ll b, ll &x, ll &y) { if(b == 0) { x = 1; y = 0; return a; } ll d = exgcd(b, a % b, y, x); y -= a/b*x; return d; } int main() { ios::sync_with_stdio(false); cin.tie(nullptr), cout.tie(nullptr); int t; cin>>t; while(t--) { ll n,k; cin>>n>>k; ll a = n/k+1,b = n/k,m = (n+1)/2,x,y; ll d = exgcd(a,b,x,y); if(m % d){ cout<<"No\n"; continue; } a /= d; b /= d; m /= d; ll xx = (ll) x * (m % b) % b; if(xx < 0) xx = xx + b; ll yy = (ll)(m - a * xx) / b; if(yy < 0 || xx > n % k) cout<<"No\n"; else { if(0<=xx&&xx<=n%k&&0<=yy&&yy<=k-(n%k))cout<<"Yes\n"; else{ ll l1 = -xx*a,r1 = (n%k-xx)*a; ll l2 = -(k-n%k-yy)*b,r2 = yy*b; ll l = max(l1,l2),r = min(r1,r2); ll ab = a*b; if(l>r)cout<<"No\n"; else if(r/ab>0&&r/ab*ab>=l)cout<<"Yes\n"; else cout<<"No\n"; } } } return 0; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix