HDU 5726 GCD [RMQ+二分]

1 篇文章 0 订阅

题干

链接:走你

题解

/**
*@author micsay
*先用RMQ预处理各个区间的gcd,然后再用二分法+map统计相同gcd区间的区间个数
*具体解题思路请看注释
**/
#include<cstdio>
#include<cmath>
#include<cstring>
#include<map>
//构建mp数组的函数
int _func(int a,int b){
  if(!a)return b;
  return _func(b%a,a);
}
typedef long long T;
typedef long long ll;
const int MAX = 100010;//原始数组大小
int root_num;//原始数组真正大小
T root[MAX];
T mp[MAX][65];//1<<65为最大可以保存的64bit整数,mp保存RMQ预处理数据
void RMQ_init(){
  memset(mp,0,sizeof(mp));
  int lg2 = log(root_num)/log(2);
  for(int i=1;i<=root_num;i++)mp[i][0]=root[i];//将root数据导入mp(初始化)
  for(int j=1;j<=lg2;j++){
     for(int i=1;i<=root_num;i++){
        //构建mp[][]数组
        //分为:区间一[i,i+(1<<(j-1))-1]和区间二[i+(1<<(j-1)),i+(1<<j)-1]两部分
        //其中,区间[i,i+(1<<j)-1]的大小为1<<j,所以正好每个区间大小为1<<(j-1)
        mp[i][j]=mp[i][j-1];
        if(i+(1<<(j-1))<=root_num)
            mp[i][j]=_func(mp[i][j-1],mp[i+(1<<(j-1))][j-1]);
     }
  }
}
T query(int l,int r){
   int lg2 = log(r-l+1)/log(2);
   //区间[l,r]大小为(r-l+1),可以分成两个区间,分别为:区间一[l,l+(1<<lg2)-1],
   //区间二[r-(1<<lg2)+1,log2](注意,两个区间会有交集,但是并不影响结果)
   return _func(mp[l][lg2],mp[r-(1<<lg2)+1][lg2]);
}
//用map记录
std::map<int,ll>V;
bool Same(const int l,int r,const T gcd){
   //看区间[l,r]的gcd是否与gcd相同
   return gcd == query(l,r);
}
//用二分法
//因为区间[i,k](i<=k<=root_num)的gcd随着k变大而呈递减状态
//所以比如区间[1,mid](mid=(1+root_num))的gcd和区间[1,root_num]的gcd相同,那么,
//就不要再遍历区间[1,p]了(mid<=p<root_num),而是直接使V[gcd]+=(root_num-mid+1)
//然后再遍历[1,mid-1]
void find(const int l,int mid,int r,const T p_gcd){
   if(l>r)return;
   if(Same(l,mid,p_gcd)){
      if(V.find(p_gcd)==V.end()){
        V[p_gcd] = ll(r-mid+1);
      }else{
        V[p_gcd] += ll(r-mid+1);
      }
      r=mid-1;
      if(l<=r)
        find(l,(l+r)/2,r,query(l,r));
      return;
   }else{
      mid = mid + (r-mid)/2 + (r-mid)%2;
      find(l,mid,r,p_gcd);
   }
}
int main(){
  int t,q;
  scanf("%d",&t);
  for(int cas=1;cas<=t;cas++){
    scanf("%d",&root_num);
    for(int i=1;i<=root_num;i++){
      scanf("%I64d",&root[i]);
    }
    RMQ_init();
    V.clear();
    //构建V
    for(int i=1;i<=root_num;i++){
       find(i,(i+root_num)/2,root_num,query(i,root_num));
    }
    scanf("%d",&q);
    printf("Case #%d:\n",cas);
    int l,r;
    while(q--){
       scanf("%d%d",&l,&r);
       ll ans = query(l,r);
       printf("%I64d %I64d\n",ans,V[ans]);
    }
  }
  return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值