HDU 5381 The sum of gcd 离线处理+线段树

9 篇文章 0 订阅
6 篇文章 0 订阅
题意:给一组序列,多次询问,每次询问 f(l,r)=ri=lrj=igcd(ai,ai+1....aj)

最初在赛场上看到这道题的时候,就想离线处理,但是怎么也不会。原因是当时并不知道这题的一个性质:从一个下标为i的数向后的连续区间 [i,i+1][i,i+2]...[i,n] 中不同的 gcd(ai,ai+1,...,aj) 最多只有 log(ai) 个。

有了这个性质我们可以干啥呢,对于每一个左端点对答案的贡献就可以就能求出来;那么我们把询问按照左端点排序,从大到小回答询问,移动左端点时,用线段树来更新答案,遇到一组询问就把询问的答案记下,最后输出即可。

至于怎么更新答案,假设我们已经知道 ai+1 的每一个gcd对应的连续区间,那么我们用 ai 与这些gcd做一次求gcd,得到一些单调不增的gcd,那么我们把相同的gcd对应的区间合并,在线段树区间修改里加上对应的gcd就行。
时间复杂度 O(nlog2n)

P.S.可以直接用树状数组维护,但是线段树无脑一些,所以直接用线段树做了。

145194772015-08-1511:23:10Accepted5381171MS2472K2659BG++Kurama
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <map>
#include <algorithm>
#define MAXN 10001
using namespace std;
struct tree{int l,r;long long mark,val;}t[MAXN*4];
//线段树 
struct query{int l,r,id;}q[MAXN];
//询问 
bool cmp(const query &a,const query &b){return a.l>b.l;}
int num[MAXN],g[MAXN],l[MAXN],r[MAXN];
long long ans[MAXN];
int res;
void built(int left,int right,int pos)
{
    t[pos].l=left,t[pos].r=right,t[pos].mark=0;
    if(left==right){t[pos].val=0;return;}
    int m=(left+right)>>1;
    built(left,m,2*pos);
    built(m+1,right,2*pos+1);
    t[pos].val=t[2*pos].val+t[2*pos+1].val;
}
//建树 
void check(int pos)
{
    if(t[pos].mark!=0){
        t[pos].val+=(t[pos].r-t[pos].l+1)*t[pos].mark;
        if(t[pos].l!=t[pos].r){
            t[pos*2+1].mark+=t[pos].mark;
            t[pos*2].mark+=t[pos].mark;
        }
        t[pos].mark=0;
    }
}
//下方标记 
void update(int left,int right,int value,int pos)
{
    check(pos);
    if(left==t[pos].l&&right==t[pos].r){t[pos].mark=value;return;}
    int m=(t[pos].l+t[pos].r)>>1;
    if(left>m)update(left,right,value,2*pos+1); 
    else if(right<=m)update(left,right,value,2*pos);
    else{update(left,m,value,2*pos);update(m+1,right,value,2*pos+1);}
    check(2*pos+1);check(2*pos);
    t[pos].val=t[2*pos].val+t[2*pos+1].val;
}
//区间更新 
long long ask(int left,int right,int pos)
{
    check(pos);
    if(t[pos].l==left&&t[pos].r==right)return t[pos].val;
    int m=(t[pos].l+t[pos].r)>>1;
    if(left>m)return ask(left,right,2*pos+1);
    else if(right<=m)return ask(left,right,2*pos);
    return ask(left,m,2*pos)+ask(m+1,right,2*pos+1);
}
//区间询问 
int _unique(int size){
    int ct=0;
    for(int i=0;i<size;ct++,i++){
        g[ct]=g[i];l[ct]=l[i];r[ct]=r[i];
        while(g[i]==g[i+1]){
            l[ct]=min(l[ct],l[i+1]);
            r[ct]=max(r[ct],r[i+1]);
            i++;
        }
    }
    g[ct]=0;
    return ct;
}
//合并相同区间 
int main()
{
    int t;
    scanf("%d",&t);
    while(t--){
        int n,cnt=0;
        scanf("%d",&n);
        for(int i=1;i<=n;i++)scanf("%d",&num[i]);
        built(1,n,1);
        int qn;
        scanf("%d",&qn);
        for(int i=1;i<=qn;i++){
            scanf("%d%d",&q[i].l,&q[i].r);
            q[i].id=i;
        } 
        sort(q+1,q+qn+1,cmp);
        //记下询问并排序 
        memset(g,0,sizeof g);
        for(int i=n,k=1;i>=1;i--){
            //更新左端点信息,cnt为不同gcd的区间数 
            for(int j=0;j<cnt;j++){
                int x=__gcd(num[i],g[j]);
                g[j]=x;
            } 
            g[cnt]=num[i];l[cnt]=r[cnt]=i;
            ++cnt;
            cnt=_unique(cnt);
            for(int j=0;j<cnt;j++)update(l[j],r[j],g[j],1);
            //对左端点的连续区间在线段树里加上贡献 
            while(q[k].l==i){
                ans[q[k].id]=ask(q[k].l,q[k].r,1);
                k++;
            }
            //回答对于这个端点的询问 
        }
        for(int i=1;i<=qn;i++)printf("%lld\n",ans[i]);
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值