2015 Multi-University Training Contest 8 (hdu5381 The sum of gcd)

Problem Description

 

Input

There are multiple test cases. The first line of input contains an integer T, indicating the number of test cases. For each test case:
First line has one integers n
Second line has n integers Ai
Third line has one integers Q,the number of questions
Next there are Q lines,each line has two integers l,r
1≤T≤3
1≤n,Q≤10^4
1≤ai≤10^9
1≤l<r≤n

Output
For each question,you need to print f(l,r)
 

Sample Input
2
5
1 2 3 4 5
3
1 3
2 3
1 4
4
4 2 6 9
3
1 3
2 4
2 3
 

Sample Output
9
6
16
18
23
10

题意:给出n个数,按要求求值。

思路:   

以下来自:https://blog.csdn.net/animalcoder/article/details/80310579
1e4 考虑莫队,每次[l,r]转移到[l-1,r]需要加上sum=gcd[l-1,l-1]+gcd[l-1,l]+gcd[l-1,l+1]....+gcd[l-1,r];   
  
由于gcd特性,gcd[l-1,l-1],gcd[l-1,l],gcd[l-1,l+1]....gcd[l-1,r]是递减的,且值的数目不会超过logn     。。。(小本本记下来) 
莫队要logn求出sum: 所以我们需要维护出lft 跟 rft  维护出累计gcd值,跟转折下标 (lft定义看下面)   
计算转移: [l,r]转移到[l-1,r]我们只要暴力遍历lft 利用转折下标的差值*该累计gcd 计算sum   

lft定义: 比如lft[2]:固定左端点2 右端点滑动2~n 对应的累计gcd值与转折下标   
  
原数组下标: 2 3 4 5 6 7 8 9 ....  13        
累计gcd:   8 8 8 8 8 4 4 2 2 2 1 1     (gcd(a2,a3),gcd(a2,a3,a4)......)  
转折下标           ↑   ↑     ↑   ↑   
lft: lft[2][0]=(6,8),lft[2][1]=(8,4),lft[2][2]=(11,2),lft[2][3]=(13,1)   
  
lft内存会不会炸?:固定左端点2 顶多logn个gcd值,所以内存不会炸   
lft维护方法:与hdu5726维护的方法一样,二分+RMQ求出转移下标跟累计gcd值 

代码思路:  
A 预处理ST  
B 预处理lft rft  
C 离线莫队,计算转移 

代码:

const int maxn=1e4+15;

struct mo
{
    int id,ql,qr;
}q[maxn];
int bel[maxn];
int col[maxn];
ll res[maxn];

struct node
{
    int pos,gcd;
    node(){}
    node(int pos,int gcd):pos(pos),gcd(gcd){};
};
vector<node>lft[maxn];
vector<node>rft[maxn];
int dp[maxn][19];
int logg[maxn];
int a[maxn];
void ST(int n)//维护区间gcd RMQ
{
    for(int i=1;i<=n;i++) dp[i][0]=a[i],logg[i]=log2(i);
    for(int j=1;(1<<j)<=n;j++)
    {
        for(int i=1;i+(1<<j)-1<=n;i++)
        {
            int x=dp[i][j-1],y=dp[i+(1<<j-1)][j-1];
            dp[i][j]=__gcd(x,y);
        }
    }
}
int RMQ(int l,int r)
{
    int len=logg[r-l+1];
    int x=dp[l][len],y=dp[r-(1<<len)+1][len];
    return __gcd(x,y);
}

int lsearch(int l,int r,int qq,int i)//固定左端点
{
    int ans=0;
    while(l<=r)
    {
        int m=l+r>>1;
        if(RMQ(i,m)>=qq) l=m+1,ans=m;
        else r=m-1;
    }
    return ans;
}
int rsearch(int l,int r,int qq,int i)//固定右端点
{
    int ans=0;
    while(l<=r)
    {
        int m=l+r>>1;
        if(RMQ(m,i)<qq)l=m+1 ;//手抖写成RMQ(i,m)一直没发现,debug两个钟
        else r=m-1,ans=m;
    }
    return ans;
}
void init(int n)//维护lft rft
{
    for(int i=1;i<=n;i++)
    {
        lft[i].clear();
        int la=i;
        int qq=RMQ(i,i);
        while(1)
        {
            int ans= lsearch(la,n,qq,i);//二分找下一个转折下标ans
            lft[i].push_back(node(ans,qq));
            if(ans==n)break;
            la=ans+1;
            qq=RMQ(i,la);
        }
        rft[i].clear();
        la=i; 
        qq=RMQ(i,i);
        while(1)
        {
            int ans=rsearch(1,la,qq,i);
            rft[i].push_back(node(ans,qq));
            if(ans==1)break;
            la=ans-1;
            qq=RMQ(la,i);
        }
    }
}
bool cmp(mo a,mo b)

    if(bel[a.ql]==bel[b.ql])return a.qr<b.qr;
    return a.ql<b.ql;
}
bool cmp2(mo a,mo b)

    return a.id<b.id; 
}
ll ans=0;
void findL(int l,int r,int ty)//莫队转移
{
    ll tem=0;int la=l;
    for(int i=0;i<lft[l].size();i++)
    {
        ll pos=lft[l][i].pos;
        ll gcd=lft[l][i].gcd;
        tem+=1ll*(min(1ll*r,pos)-la+1)*gcd;
        la=pos+1;
        if(la>r)break;
    }
    if(ty==1)ans+=tem;
    else ans-=tem;
}
void findR(int l,int r,int ty)//莫队转移
{
    ll tem=0;
    int la=r;
    for(int i=0;i<rft[r].size();i++)
    {
        ll pos=rft[r][i].pos;ll gcd=rft[r][i].gcd;
        tem+=1ll*(la-max(1ll*l,pos)+1)*gcd;
        la=pos-1;
        if(la<l)break;
    }
    if(ty==1) ans+=tem;
    else ans-=tem;
}
int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
        int n;
        scanf("%d",&n);
        int unit=(int)sqrt(n);//莫队分块套路
        for(int i=1;i<=n;i++)scanf("%d",&a[i]),bel[i]=i/unit+1;
        ST(n);
        init(n);
        int qqq;
        scanf("%d",&qqq);
        int idx=0;
        while(qqq--)
        {
            idx++;
            scanf("%d %d",&q[idx].ql,&q[idx].qr);
            q[idx].id=idx;
        }
        int l=1,r=0;
        sort(q+1,q+1+idx,cmp);
        ans=0;
        for(int i=1;i<=idx;i++)
        {
            while(r<q[i].qr)r++,findR(l,r,1);
            while(r>q[i].qr)findR(l,r,-1),r--;
            while(l>q[i].ql)l--,findL(l,r,1);
            while(l<q[i].ql)findL(l,r,-1),l++;
            res[q[i].id]=ans;
        }
        for(int i=1;i<=idx;i++)  printf("%lld\n",res[i]);
    }
}
 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值