关于exgcd的总结 我们主要讨论的是
1.exgcd算法 1.1 关于解的存在性 有裴蜀定理 知,对于方程 存在解的充分必要条件是:
tips:裴蜀定理 如果 均为整数,则有整数 使得 ,这个等式称为裴蜀等式。
1.2exgcd算法介绍 要求 ,我们可以用 求出 。令 。
当我们求出 之后呢,求出的 乘上 就是答案啦。
具体处理方法就是:
然后对比两边得到: 然后只要一直递归下去就 啦。
注意边界条件 : 时候, 是解。
1 2 3 4 5 6 7 8 9 10 11 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关于 的解 我们知道,对于 的题解是:
方程是否有正整数解?我们发现,
当同号 时: 负相关,即 越大 越小。当求出的 是最小的时候,有 是最大的。
当异号 时, 正相关,即同增同减。
所以,一个合理的想法,我们对 传参时候保证 的非负性 。即:若 ,那么传入 ,最后注意讨论变化。
那么如何求解最小正整数解?由通解可知:
*( ) **,相当于保证 的情况下减去尽可能多的 。注意如果等于 了的话再处理一下。
通过 就很容易得到
的 最小的非负整数解 的板子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 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.正整数解
正整数解的个数为: 或者
当 时有正解
当 时无解
当 时,恰有 个正整数有非负解,具体是哪些不清楚。
推论:
设 且 时, 是不能表示成 形式的最大整数。
设 且 时, 是不能表示成 形式的最大整数。
显然如果 可以是负数,那可以表示为任意整数了。
有解的充要条件是方程 有非负解。
exgcd的板子题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 #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 minx,miny,maxx,maxy; minx = x,maxy = y; y = (y%a+a)%a; y = y==0 ?a:y; x = (c-b*y)/a; maxx = x,miny = y; ll cnt = (maxx-minx)/b+1 ; cout<<cnt<<" " <<minx<<" " <<miny<<" " <<maxx<<" " <<maxy<<"\n" ; } else { ll minx,miny; minx = x; y = (y%a+a)%a; y = y==0 ?a:y; miny = y; cout<<minx<<" " <<miny<<"\n" ; } } } return 0 ; }
1.5*取模意义下的一元二次方程的最小值 起源是关于这个题 中提取的思路。
能组成的最小正整数是 。但是在取模意义下 呢?
考虑以下两个问题:
的最小值
的最小值
先说结论 :
的最小值是:
的最小值是
①先讨论第一个: 的最小值
那么我们只要考虑 意义下的最小值即可。
设 ,利用同余 性质可知 的 的倍数,于是 ,移向得 。
tips:同余 设 是正整数,若 是整数,且 ,则称 和 模 同余。或者说 除以 余数为 。
显然上面这个柿子已经没有模的意义了 ,那么上面这个 最小显然是
那么 的最小值是
②那么对于 呢?
上面这个柿子的含义就是要保证 的前提下减去尽可能多的 ,那也就是
有了上面的分析,下面来说这个题 就很容易了。
题目是要求: 的最小值,并求出 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 #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; ll g1 = exgcd (a,b,x,y); ll k1,t; ll g2 = exgcd (g1,m,k1,t); 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 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 #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); x = -x; x = (x%(B/d)+(B/d))%(B/d); if (A*x!=0 ) { ans = min (ans,A*x); } } cout<<ans<<"\n" ; return 0 ; }
思路:上面讲解exgcd的时候有讲,主要思想就是,先去除取模的意义 。什么意思呢?
就是说对于 后的最小值即 的最小值
即 的最小值。这样我们处理出没有取模意义的等式就可以计算啦。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 #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); 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 ; }
思路:不妨假设
显然的 时,
因为不可能同时为负数,那么当 异号的时候呢?
不妨假设
对于以下的讨论不妨假设 且 ,于是:
含义就是喝 杯 ,贡献是 ,再喝 杯 水。
对于 需要分 步考虑:
将 倒满
将 中水导入 ,此时杯中: 的水
将 的水喝掉
(*)将第二个杯子里面的 倒掉(如果是最后一步就不要这一步)
那么
最后答案就是
接下来我们只需先求出一个特解,然后在特解附件去求它的通解就可以了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 #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 { 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 ; }
思路:由于我们只能在一个店买,该商店红辣椒 份一包,黑辣椒 份一包,且只能买整包的。那么要求买到份数恰好是 ,那么就有 ,含义是在第 家店买 包红的和 包黑的总共 份。
第 道菜用了红辣椒可以增加 的美味值,用黑的增加 。
我们设 为买 份红的可以达到的最大美味值。
我们先考虑一个简单的问题:买 份红的, 份黑的的最大美味值是多少?
让我们感性的去理解一下:假设我全买了黑的,再用贪心的策略把其中 份换成红的。
全买黑的贡献 ,对于第 道菜换成红的
那么我们按照 从大到小排序,贪心的去选那 个红的。
我们观察 是单减的,可能由某一个(可能是正数)逐渐减小变成负数。
那么我们 由一个先增后减的趋势,是个有极值的函数(开口向下)。
那么处理好了 ,接下来考虑询问。
很显然 的解用 可解。
我们先求出来了 的一个特解
如果说 ,那就无解直接 。
否则的话继续讨论。
特解知道了,我们可以进一步求出它们的通解: 回归问题
对于
因为 是有极值的单峰函数,我们可以暴力 一遍找到最大值和最大值 的横坐标
对于通解:
显然 最小取 ,最大取
那么对于通解的最小值就是 ,最大值就是
然后我们在从任意集中,选择离 下标最近的两组,取两者的 即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 #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 ; } ll d = a/g*b; 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 ; }
题意:给定 .要求找出 中的 的数量,使得 满足 。
思路:题目要求
我们移向得: ,写成我们常见写法就是 。
显然我们可以用 直接判无解的情况 。那么对于有解的情况呢?
首先因为参数有负数,那么我们可以知道, 是同增同减的。我们可以求得一组最小的 。怎么求呢?
我们可以直接通过 求得一组特解 。我们令
那么通解就是: 又由于 是同增同减的,那么我们要找到一组最小的解,当且仅当:
或
我们令
那么我们的通解就变成了: 因为我们要求 有多少属于 的,那么我们把 代入 得:
那么可以取的 的个数就是我们的答案了。
即:
接下来我们只需要求出 和 即可,答案就是
注意:取整要手写,因为当其是负数时候,c++默认往0取整,所以下取整我们要自己写
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 #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) { 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 ; }
思路:这个题是关于 的题,在说这个题之前,我们先说下面这两个题目
题意:一共有 种剑,并且每种初始值为 把,然后进来 个人,他们在剧院的地下室没人拿走 把剑(这z把剑是属于同一种类型的),并且不同的人可以拿同种的剑;现在给出每种还剩下的剑,问最小的 值是多少?
思路:很显然,因为初始每个种类数量是一样的,可以确定初始每个的数量 是当前剩余量的最大值,因为如果 比最大的还大,那我不就需要更多人取拿了吗,显然不是最优的了,那么我们令 ,表示每个类型的消失数量。又因为每个人只能拿 把同类型的剑,并且不同人可以拿同一种。那么答案显然是 .
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 #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) 题意:给定长度为 的序列 ,下标从 ,每次操作构造新的序列 ,满足 ,再将 序列替换为序列 ,问最少多少次操作后满足 序列所有元素相同。
思路:考虑对于~ 的元素,考虑变化一次每个元素变为了什么?举个例子: .变化一次之后就是 。那么变化两次呢?
看到这里,变化就很明显了。题目问的是最小操作次数,也很显然,这个操作次数满足单调性,因为 次满足那么 次也一定满足。那么考虑二分 。如何 呢?就是确定最小前缀 ,并且后面的 都等于 。
那么我们需要一个快速处理出区间 的方法,那显然就是 表啦,当然线段树也可以。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 #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 { 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 ; }
说完上面两个题,接下来来说这个 题
题意:
给定 和一个长度为 的数组 ,求一个最长 的区间 ,使得存在 和 ,对于所有 (即区间内所有数对 取模余数相等 ),输出最长区间长度(区间长度定义为 )。
有多组测试数据 。
思路:令
那么就有:
对于任意两项:比如
即:
那结论就是:若 在 的意义下相等, 一定能整除
也就是说,任意两项差是 的倍数。
我们可以对原序列做差 得到新数列,因为 ,那么新数列符合条件的区间 一定不为 。(因为不妨设最后取模之后为0,区间的 就是我们求的 )
我们考虑枚举 ,然后二分 。 部分用 表。
小细节:
多组数据,注意清空
特判 的情况
因为做差得的新数组只有 项,最大子区间长度应该+1才是答案。
开
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 #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 { 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; 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 ; }
思路:思维+
我们可以把题目看成一个长度为 的区间在长度为 的数组上滑动。每当我们前进一格,如果出去一个 ,那么下一个必须进来一个 ,如果出去一个 ,那么下一个必须进来一个 。那意味着什么呢?
对于下标为: 上的奇偶性要是一样的,即 。
我们考虑按上述方法把原序列按照下标分成 块,每块去分配奇偶性。
包含数字个数为 的块数有 块,包含数字个数为 的块数有 块。
又由于整个序列上应该有 个位置是奇数,有 个位置上是偶数。
那么就考虑
含义就是:取 个长度为 的,和 个长度为 的放奇数(恰好填满所有奇数 ,剩下的放偶数就可以了。那么只需要 我们的 是不是满足 。
如何判断呢?考虑先用 求出一个特解 。
那么通解就是 。
那么:由 于 要 求 是 整 数 , 就 是 说 ② ④ 式 有 分 数 , 为 了 处 理 这 个 问 题 , 两 边 同 乘 得 接下来看这两个不等式两边有没有交集,再确定区间里面有没有 的倍数,这样能保证是整数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 #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 ; }