HDU-5726-GCD(GCD预处理 + 线段树维护区间)

链接: HDU-5726-GCD

题意:
给你n个数(n<=1e5)然后m个询问(m<=1e5),每个询问一个区间,问你这个区间的GCD是多少,并且输出从1到n有多少个区间的GCD和这个区间的相同。

思路:
首先我们需要知道区间gcd的性质:
假设我们固定右端点,区间向左延伸,那么gcd个数一定是呈现非递增性质的,即要么不变要么减小,并且减小的话一定减少一半,因为实际上一段区间内,不同的gcd的个数,不会超过log个。

第一部分可以用线段树维护区间的GCD,第二部分,我们可以固定一个右端点,找出每一个gcd值变化的左端点,这个区间的长度就是区间gcd出现的次数。类似于递推的思路,具体看代码。

代码:

#include <iostream>
#include <cstdio>
#include <queue>
#include <math.h>
#include <map>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
const int maxn = 1e6 + 7;
typedef long long ll;
const int mod=1e9+7;
int T;
int n,j;
int a[maxn];
int sum[maxn];
int v[maxn],l[maxn];
map<int ,ll >mp;
void build(int l,int r , int rt){
    if(l == r){
      sum[rt] = a[l];
      return ;
    }
    int mid = (l + r) / 2;
    build(l , mid , rt << 1);
    build(mid + 1 , r , rt << 1 | 1);
    sum[rt] = __gcd(sum[rt << 1] , sum[rt << 1 | 1]);
}
int query(int L,int R,int l, int r , int rt){
    if(L <= l && R >= r){
        return sum[rt];
    }
    int ans = 0;
    int mid = (l + r) / 2;
    if(L <= mid) ans = __gcd(ans , query(L , R , l , mid , rt << 1));
    if(R > mid) ans = __gcd(ans , query(L , R , mid + 1 , r , rt << 1 | 1));
    return ans;
}
//l[j] 表示满足区间[j , i]和区间[l , i]的gcd相同的最小l值。
//v[j] 表示区间[j , i]的gcd。
void init(){
    mp.clear();
    for(int i = 1; i <= n; i ++){
        for(j = i,l[j] = i,v[j] = a[i]; j != 0 ; j = l[j] - 1){
            v[j] = __gcd(v[j] , a[i]);
            while(l[j] > 1 && __gcd(a[i] , v[l[j] - 1]) == v[j]){
                l[j] = l[l[j] - 1];
            }
            mp[v[j]] += j - l[j] + 1;
        }
    }
}
int main(){
    int ca = 1;
    scanf("%d",&T);
    while(T--){
        scanf("%d",&n);
        for(int i = 1; i <= n; i ++){
            scanf("%d",&a[i]);
        }
        build(1 , n , 1);
        init();
        int t , l , r;
        scanf("%d",&t);
        printf ("Case #%d:\n",ca++);
        while(t--){
            scanf("%d%d",&l,&r);
            int ans = query(l , r , 1 , n , 1);
            printf ("%d %lld\n",ans,mp[ans]);
        }
    }
}


代码2:

#include <iostream>
#include <cstdio>
#include <queue>
#include <math.h>
#include <map>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
const int maxn = 1e6 + 7;
typedef long long ll;
const int mod=1e9+7;
int T;
int n,j;
int a[maxn];
int sum[maxn];
int v[maxn],l[maxn];
map<int ,ll >mp;
vector<pair<int , int>> vec[maxn];
void build(int l,int r , int rt){
    if(l == r){
      sum[rt] = a[l];
      return ;
    }
    int mid = (l + r) / 2;
    build(l , mid , rt << 1);
    build(mid + 1 , r , rt << 1 | 1);
    sum[rt] = __gcd(sum[rt << 1] , sum[rt << 1 | 1]);
}
int query(int L,int R,int l, int r , int rt){
    if(L <= l && R >= r){
        return sum[rt];
    }
    int ans = 0;
    int mid = (l + r) / 2;
    if(L <= mid) ans = __gcd(ans , query(L , R , l , mid , rt << 1));
    if(R > mid) ans = __gcd(ans , query(L , R , mid + 1 , r , rt << 1 | 1));
    return ans;
}
void init(){
    for(int i = 1; i < maxn; i ++){
        vec[i].clear();
    }
    mp.clear();
    for(int i = 1; i <= n; i ++){
        vec[i].push_back({i , a[i]});
        int pre = a[i];
        for(int j = 0; j < vec[i - 1].size(); j ++){
            int g = __gcd(vec[i - 1][j].second , a[i]);
            if(g != pre){
                vec[i].push_back({vec[i - 1][j].first , g});
                if(vec[i].size() > 1) mp[pre] += vec[i][vec[i].size() - 2].first - vec[i][vec[i].size() - 1].first;
                pre = g;
            }
        }
        int g = vec[i][vec[i].size() - 1].second;
        mp[g] += vec[i][vec[i].size() - 1].first;
    }
}
int main(){
    int ca = 1;
    scanf("%d",&T);
    while(T--){
        scanf("%d",&n);
        for(int i = 1; i <= n; i ++){
            scanf("%d",&a[i]);
        }
        build(1 , n , 1);
        init();
        int t , l , r;
        scanf("%d",&t);
        printf ("Case #%d:\n",ca++);
        while(t--){
            scanf("%d%d",&l,&r);
            int ans = query(l , r , 1 , n , 1);
            printf ("%d %lld\n",ans,mp[ans]);
        }
    }
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值