计蒜客44990 Be Geeks!(递归,st表)

题意:

在这里插入图片描述

解法:

对于M(i,j),肯定是想对于每个值,计算他能作为最大值的区间,

知道了M(i,j),G(i,j)怎么办?

因为区间gcd每增加一个数,一定是非递增的,因此对于固定的M,G一定是这样的:

在这里插入图片描述
即M的两边一定是一段一段的,而且是递减的,最多log段。
因为M是固定的,G的每一段是相同的,(每一段的长度可以用二分一个一个求出来),
那么选取左边的一段和右边的一段进行匹配,统计答案,
可以两层循环枚举,复杂度是O(log*log)的。

区间gcd的区间最值点可以用st表快速计算。

-----分割线-----
一开始计算求出整个序列的最大值位置m,计算出当前a[m]作为最大值的答案之后,
递归处理[1,m-1]和[m+1,r]。

ps:
如果存在相同的最大值,第一层是其中一个占用整个序列,
第二层是另外一个最大值切割后(递归的时候切割了)的某段序列,
这样好像能保证不重复计算。

code:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int maxm=2e5+5;
const int mod=1e9+7;
int lg2[maxm];
int a[maxm];
int n;
struct ST{
    int m[maxm][25];
    int g[maxm][25];
    static const int maxd=20;
    void init(){
        for(int i=1;i<=n;i++){
            m[i][0]=i;
            g[i][0]=a[i];
        }
        for(int j=1;j<=maxd;j++){
            for(int i=1;i+(1<<j)-1<=n;i++){
                if(a[m[i][j-1]]>a[m[i+(1<<(j-1))][j-1]])m[i][j]=m[i][j-1];
                else m[i][j]=m[i+(1<<(j-1))][j-1];
                //
                g[i][j]=__gcd(g[i][j-1],g[i+(1<<(j-1))][j-1]);
            }
        }
    }
    int m_ask(int l,int r){
        int k=lg2[r-l+1];
        int x=m[l][k],y=m[r-(1<<k)+1][k];
        return a[x]>a[y]?x:y;
    }
    int g_ask(int l,int r){
        int k=lg2[r-l+1];
        return __gcd(g[l][k],g[r-(1<<k)+1][k]);
    }
}T;
void lg2_init(){
    lg2[1]=0;
    for(int i=2;i<maxm;i++){
        lg2[i]=lg2[i-1];
        if((i&(i-1))==0)lg2[i]++;
    }
}
int solve(int l,int r){
    if(l>r)return 0;
    int m=T.m_ask(l,r);
    vector<int>L,R;
    L.push_back(m+1);
    R.push_back(m-1);
    //right
    int p=m;
    while(p<=r){
        int g=T.g_ask(m,p);
        int lc=p,rc=r;
        int pos=-1;
        while(lc<=rc){
            int mid=(lc+rc)/2;
            if(T.g_ask(m,mid)==g){
                pos=mid,lc=mid+1;
            }else{
                rc=mid-1;
            }
        }
        R.push_back(pos);
        p=pos+1;
    }
    //left
    p=m;
    while(p>=l){
        int g=T.g_ask(p,m);
        int lc=l,rc=p;
        int pos=-1;
        while(lc<=rc){
            int mid=(lc+rc)/2;
            if(T.g_ask(mid,m)==g){
                pos=mid,rc=mid-1;
            }else{
                lc=mid+1;
            }
        }
        L.push_back(pos);
        p=pos-1;
    }
    //
    int ans=0;
    for(int i=1;i<(int)L.size();i++){
        for(int j=1;j<(int)R.size();j++){
            int gg=T.g_ask(L[i],R[j]);
            int temp=1ll*(L[i-1]-L[i])*(R[j]-R[j-1])%mod;
            ans=(ans+1ll*a[m]*gg%mod*temp%mod)%mod;
        }
    }
    return (1ll*ans+solve(l,m-1)+solve(m+1,r))%mod;//int是2e9,这里三个加起来最大3e9,可能会爆,所以加ll
}
signed main(){
    lg2_init();
    scanf("%d",&n);
    for(int i=1;i<=n;i++)scanf("%d",&a[i]);
    T.init();
    int ans=solve(1,n);
    printf("%d\n",ans);
    return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值