RMQ求区间gcd

RMQ模板请看 https://blog.csdn.net/qq_43333395/article/details/89442824
首先来一道航电例题(hdu 5726)
题目链接:
http://acm.hdu.edu.cn/showproblem.php?pid=5726

题意: 给一个数组a,大小为n,接下来有m个询问,每次询问给出l、r,定义f[l,r]=gcd(al,al+1,…,ar),问f[l,r]的值 和 有多少对(l’,r’)使得f[l’,r’]=f[l,r]。n<=10万,m<=10万,1<=l<=r<=n,1<=l’<=r’。

思路:
第一步比较简单,预处理一下,定义f[i][j]为:ai开始,连续2^j个数的最大公约数,所以f[1][0]=a[1],f[1][1]=gcd(a1,a2),f[1][2]=gcd(a1,a2,a3,a4)。其实就是动态规划,让i从1-n,让j从0-17,递推上去即可。
  递推公式如下:
  1. f[i][0]=a[i];
  2. f[i][j]=gcd(f[i][j-1],f[i+(1<<j-1)][j-1]);
  就如同f[1][2]=gcd(f[1][1],f[3][1])=gcd(gcd(f[1][0],f[2][0]),gcd(f[3][0],f[4][0]));

通过上述预处理,查询时就只需O(logn)时间,如下:

令k=log2(r-l+1),look(l,r)=gcd(f[l][k],f[r-(1<<k)+1][k]);

注:f[l][k] 和 f[r-(1<<k)+1][k]可能会有重叠,但不影响最终的gcd值。

比赛时第二步没想出来,太可惜了。。。

第二步,我们可以枚举左端点 i 从1-n,对每个i,二分右端点,计算每种gcd值的数量,因为如果左端点固定,gcd值随着右端点的往右,呈现单调不增,而且gcd值每次变化,至少除以2,所以gcd的数量为nlog2(n)种,可以开map<int,long long>存每种gcd值的数量,注意n大小为10万,所以数量有可能爆int。

#include<bits/stdc++.h>
using namespace std;
#define LL long long
int dp[100010][32];
int a[100010];
int n,m,g,j;
int gcd(int a,int b){
    return b?gcd(b,a%b):a;
}

void rmq_init(){
    for(int j=1;j<=n;j++) dp[j][0]=a[j];
    for(int i=1;(1<<i)<=n;i++){
        for(int j=1;j+(1<<i)-1<=n;j++){
             dp[j][i]=gcd(dp[j][i-1],dp[j+(1<<(i-1))][i-1]);
        }
    }
}

int rmq_qui(int l,int r){
    int k=(int)log2(double(r-l+1));
    return gcd(dp[l][k],dp[r-(1<<k)+1][k]);
}

map<int,long long>mp;
void setTable(){
    mp.clear();
    for(int i=1;i<=n;i++){
         g=dp[i][0],j=i;  
         while(j<=n){
            int l=j,r=n;
            while(l<r){
                int mid=(l+r+1)>>1;
                if(rmq_qui(i,mid)==g) l=mid;
                else r=mid-1;
            }
            mp[g]+=l-j+1;  //相当于二分枚举相加
            j=l+1;
            g=rmq_qui(i,j);
         }
    }
}

int main(){
    int t,l,r;
    int cas=1;
    scanf("%d",&t);
    while(t--){
        printf("Case #%d:\n",cas++);
        scanf("%d",&n);
        for(int i=1;i<=n;i++) scanf("%d",&a[i]);
        rmq_init();
        setTable();
        scanf("%d",&m);
        for(int i=0;i<m;i++){
            scanf("%d %d",&l,&r);
            int g=rmq_qui(l,r);
            printf("%d %lld\n",g,mp[g]);
        }
    }
}

再来一道华工校赛的题目:https://ac.nowcoder.com/acm/contest/625/H
题目大意:
给你n个数,让你求所有l到r的gcd之和,答案mod1000000007 (l,r∈(1,n))
是RMQ进行 O(n log n log 109)的预处理搞出区间GCD值,然后每次就可以 O(log109)回答任意区间的GCD值。之后为了计算所有区间的GCD值之和,我们考虑O(n) 枚举区间左端点,然后每次右端点从左往右二分+RMQ找到第一个会让区间GCD变化的位置,并累加答案。注意到右端点从左往右扫的过程中,GCD至多只会变化O(log109) 次,所以这样做总复杂度O(n log n log2109)就变成了 。

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
int dp[500010][20];
int a[500010];
int len[500010];
int n,m,g,j;
const LL mod=1e9+7;
int gcd(int a,int b){
    return b?gcd(b,a%b):a;
}

void rmq_init(){
    for(int j=1;j<=n;j++) dp[j][0]=a[j];
    for(int i=1;(1<<i)<=n;i++){
        for(int j=1;j+(1<<i)-1<=n;j++){
             dp[j][i]=gcd(dp[j][i-1],dp[j+(1<<(i-1))][i-1]);
        }
    }
    for(int i=1;i<=n;i++) {  //存len数组可以减少运算的时间,很重要,不然会tle
        len[i]=len[i-1];
        if((1<<(len[i]+1))<i) len[i]++;
    }
}

int rmq_qui(int l,int r){
    int k=len[r-l+1];
    return gcd(dp[l][k],dp[r-(1<<k)+1][k]);
}

map<int,long long>mp;
void setTable(){
    LL ans=0;
    mp.clear();
    for(int i=1;i<=n;i++){
         g=dp[i][0],j=i;
         while(j<=n){
            int l=j,r=n;
            while(l<r){
                int mid=(l+r+1)>>1;
                if(rmq_qui(i,mid)==g) l=mid;
                else r=mid-1;
            }
            ans=(ans+LL(l-j+1)*g)%mod;
            j=l+1;
            g=rmq_qui(i,j);
         }
    }
    printf("%lld\n",ans);
}

int main(){
    int t,l,r;
        scanf("%d",&n);
        for(int i=1;i<=n;i++) scanf("%d",&a[i]);
        rmq_init();
        setTable();
}

猜你喜欢

转载自blog.csdn.net/qq_43333395/article/details/89915228
今日推荐