区间同余问题

区间同余问题

例题 :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)\)。这个其实画一下数轴就可以直观地感受出来了。

posted @ 2024-12-07 17:08  Hanggoash  阅读(48)  评论(0)    收藏  举报
动态线条
动态线条end