HDU 5869 求区间中不同连续序列的gcd的个数(树状数组)

题意:

长度n的序列, m个询问区间[L, R], 问区间内的所有子段的不同GCD值有多少种.

思路:

区间GCD收敛的很快,所以直接暴力预处理出到每个数字截至的后缀串有哪些GCD以及它们的位置,就是每个数字向前看有哪些GCD出现,这个数量是很少的。

1.枚举区间的右坐标,然后枚举出所有的以这个为右坐标为区间左坐标。
2.并求出他们这个连续区间的gcd,去重,(即重复的不再记录)
3.然后对区间以右坐标为基准进行枚举各个左坐标中对应的gcd,
(利用的性质有:1.右坐标为基准,从右向左枚举左坐标,对应的连续区间的gcd一定是不递增的性质;
     2.右坐标为基准存的gcd不存在重复的情况,每一次枚举一个就可以看成是这个坐标位置上的个数加1,
     所以可以利用树状数组进行区间查询与修改。

树状数组统计的原理类似

[1,1]

[2,2][1, 2]

[3,3][2, 3][1,2,3]

[4,4 ][3, 4][2,3,4][1,2,3,4]

前面统计的数量可以影响到后面的区间,有的影响是有必要的,有的是没有必要的。

例如 对于[1--4] 区间,应该是先计算[1,1]

   再计算[1,2][2,2]->[1-3][2,3][3,3]->[1-4][2-4][3,4][4,4];

因为vec1[i]存的是以 i结尾的连续序列的gcd ,所以还需要依赖 vec1[i-1] 的结果,或者去重。


#include<bits/stdc++.h>
#define LL long long
#define bug puts("**************")
using namespace std;
const int N=110000;
int a[N];
int pre[N*10];
int sum[N];
int gcd(int a,int b){
    return b==0?a:gcd(b,a%b);
}
int n,m;
vector<pair<int,int> > vec1[N],vec2[N];
///vec1[i] i为右边下标 first为左边下标     second 为 [j,i]连续区间元素的gcd
///vec2[i] i为右边下标 first为左边坐标     second为序号(第几个访问的)

int lowbit(int x){
    return x&(-x);
}
void add(int x,int d){
    while(x<=n){
        sum[x]+=d;
        x+=lowbit(x);
    }
}
int getsum(int x){
    int ans=0;
    while(x){
        ans+=sum[x];
        x-=lowbit(x);
    }
    return ans;
}
int ans[N];
void solve(){
    memset(sum,0,sizeof(sum));
    memset(pre,0,sizeof(pre));

    ///此区间是逐渐增大的,并且是包含关系,所以可以用树状数组
    for(int i=1;i<=n;i++){        ///从小到大 枚举右坐标,
        int len=vec1[i].size();
        for(int j=0;j<len;j++){
            pair<int,int> tmp=vec1[i][j];    ///表示以i结尾的连续序列的gcd

            if(pre[tmp.second]) add(pre[tmp.second],-1);   ///去除以前统计过的,去重,(为了统计不同的个数)
            pre[tmp.second]=tmp.first;
            add(tmp.first,1);
        }

        len=vec2[i].size();
        for(int j=0;j<len;j++){
            pair<int,int>tmp=vec2[i][j];

            ///访问顺序      ///右坐标   左坐标
            ans[tmp.second]=getsum(i)-getsum(tmp.first-1);
        }
    }
    //bug;
    for(int i=1;i<=m;i++){
        printf("%d\n",ans[i]);
    }
}
int main(){

    while(~scanf("%d%d",&n,&m)){
        memset(vec1,0,sizeof(vec1));
        memset(vec2,0,sizeof(vec2));
        for(int i=1;i<=n;i++){
            scanf("%d",&a[i]);
        }

        for(int i=1;i<=n;i++){
            int pos=i;
            int len=vec1[i-1].size();
            int tmpval=a[i];
            vec1[i].push_back(make_pair(i,a[i]));
            for(int j=0;j<len;j++){
                pair<int,int> tmp=vec1[i-1][j];
                int Gcd=gcd(tmp.second,tmpval);
                pos=tmp.first;
                if(tmpval!=Gcd){
                    vec1[i].push_back(make_pair(pos,Gcd));
                    tmpval=Gcd;
                }
            }
        }
        for(int i=1;i<=m;i++){
            int u,v;
            scanf("%d%d",&u,&v);
            vec2[v].push_back(make_pair(u,i));
        }
//        for(int i=1;i<=n;i++){
//               // cout<<vec1[i].size();
//            for(int j=0;j<vec1[i].size();j++){
//                printf("%d ",vec1[i][j].second);
//            }
//            puts("");
//        }

        solve();
    }
    return 0;
}

/*******************************

*******************************/


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值