区间同余问题
例题 :CF2050F Maximum modulo equality
题意
给定一个长度为 \(n\) 的序列 \({a_n}\),有 \(Q\) 个询问,每次询问给定一个区间 \([l,r]\) ,让你找一个最大的 \(m\),使得区间内所有的 \(a_i\mod m\) 相同,可以证明一定存在这样一个 \(m\) (\(1\))。
分析
看着很头痛,因为完全不知道从哪里入手,所以只做了这样一个转化 \(a_i=k_i\times m+t\),然后就不知道该怎么办了。
假设这样一个 \(m\) 满足题意的话,那么所有的 \(a_i\) 其实都必须可以写成这样的形式,也就是 \(a_l=k_l\times m+t,a_{l+1}=k_{l+1}\times m+t,...,a_r=k_r\times m+t\),变量的个数仍然比较多,还是没有任何头绪啊。
这时候你会发现这实际上是一个线性方程组,从线性代数的角度思考,实际上任意两个非齐次方程相减就可以得到一个齐次方程,虽然和这个地方差别还是很大,不过可以尝试去沿袭这个思路。
比如对于 \(a_1,a_2\) 来说,把两个等式相减,有 \(abs(a_1-a_2)==abs(k_1-k_2)\times m\),这就意味着 \(m\) 一定是 \(abs(a_1-a_2)\) 的因子。那么我们只需要保证 \([l,r]\) 内的 \(n^2\) 对都满足这个条件即可,也就是求这 \(n^2\) 对的 GCD,但是这会超时,仔细思考,\(n^2\) 对是不是太多了?实际上我们只需要对每一对相邻的进行这种操作,就可以保证所有对都可以被线性表示出来,也自然就满足了所有的限制(这个地方自己推一下就知道了)。
考虑 \(m\) 可以无限大的情况,肯定是区间每一个数都相等,这时候根据算法算出来的是 \(0\),刚好也和题目要求契合了。
时间复杂度允许的情况下可以直接 \(O(n)\) 查询,但是如果只能 \(O(\log n)\) 以下的话,只能用一些RMQ了。
但是这样就要注意,在合并两个区间的时候,不能直接把两份答案gcd起来,这样的话,并不能保证每一对都满足限制条件,比如各从两个区间内选一个,那么就不是满足的。因此我们在合并两个区间的时候还要注意 随便从两个区间里面分别取一个数gcd一下,然后丢到答案的GCD里面去。
这里ST表取的直接就是 \(a_l,a_r\),线段树取的是 \(a_{mid},a_{mid+1}\) 。
注意
gcd不能写成 return x%y==0?y:gcd(y,x%y) 。不然会RE。
Code_ST
#include<bits/stdc++.h>
using namespace std;
inline int gcd(int x,int y){return y==0?x:gcd(y,x%y);}
int T,n,q,l,r;
int a[200010],c[200010];
int GCD[200010][22];
inline int query(int l,int r)
{
if(l==r)return 0;
int loglen=(int)log2(r-l+1);
// cout<<l<<' '<<r<<' '<<loglen<<'\n';
// cout<<a[l]<<' '<<a[r]<<' '<<GCD[l][loglen]<<' '<<GCD[r-(1<<loglen)+1][loglen]<<'\n';
return gcd(abs(a[l]-a[r]),gcd(GCD[l][loglen],GCD[r-(1<<loglen)+1][loglen]));
}
inline void solve()
{
cin>>n>>q;
for(int i=1;i<=n;++i)cin>>a[i];
for(int j=1;(1<<j)<=n;++j)
{
for(int i=1;i+(1<<j)-1<=n;++i)
{
if(j>1)GCD[i][j]=gcd(abs(a[i]-a[i+(1<<j-1)]),gcd(GCD[i][j-1],GCD[i+(1<<j-1)][j-1]));
else GCD[i][j]=abs(a[i]-a[i+(1<<j-1)]);
}
}
while(q--)
{
cin>>l>>r;
// query(l,r);
cout<<query(l,r)<<'\n';
}
cout<<'\n';
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>T;
while(T--)
{
solve();
}
return 0;
}
COde_Seg
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+10;
struct SEG
{
int l,r,val;
}tr[N<<2];
int a[N];
int T,n,q,l,r;
inline int gcd(int x,int y){return y==0?x:gcd(y,x%y);}
inline void pu(int x)
{
tr[x].val=gcd(abs(a[tr[x].l]-a[tr[x].r]),gcd(tr[x<<1].val,tr[x<<1|1].val));
}
inline void build(int x,int l,int r)
{
tr[x].l=l,tr[x].r=r;
if(l==r){tr[x].val=0;return ;}
int mid=l+r>>1;
build(x<<1,l,mid),build(x<<1|1,mid+1,r);
pu(x);
}
inline int query(int x,int l,int r)
{
if(l<=tr[x].l&&tr[x].r<=r)
return tr[x].val;
int mid=tr[x].l+tr[x].r>>1;
if(l>mid)return query(x<<1|1,l,r);
else if(r<=mid)return query(x<<1,l,r);
else return gcd(abs(a[mid]-a[mid+1]),gcd(query(x<<1,l,r),query(x<<1|1,l,r)));
}
inline void solve()
{
cin>>n>>q;
for(int i=1;i<=n;++i)cin>>a[i];
build(1,1,n);
while(q--)
{
cin>>l>>r;
if(l==r){cout<<0<<' ';continue;}
cout<<query(1,l,r)<<' ';
}
cout<<'\n';
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>T;
while(T--)
{
solve();
}
return 0;
}
延伸
这让我想到了之前有一场 div2 的 A,就是给定一个 \(x,y\),然后求一个最小的 \(t\),使得\(t\mod x=t\mod y\),这种情况可以证明最小的能够找到的 \(m\) 一定是\(lcm(x,y)\)。这个其实画一下数轴就可以直观地感受出来了。
标签:return,gcd,int,times,问题,GCD,区间,同余 From: https://www.cnblogs.com/Hanggoash/p/18592418