st表

2.数字对

【题目描述】

小H是个善于思考的学生,现在她又在思考一个有关序列的问题。

她的面前浮现出一个长度为n的序列{ai},她想找出一段区间[L, R](1 <= L <= R <= n)。

这个特殊区间满足,存在一个k(L <= k <= R),并且对于任意的i(L <= i <= R),ai都能被ak整除。这样的一个特殊区间 [L, R]价值为R - L。

小H想知道序列中所有特殊区间的最大价值是多少,而有多少个这样的区间呢?这些区间又分别是哪些呢?你能帮助她吧。

 

【输入格式】

       第一行,一个整数n.

       第二行,n个整数,代表ai.

 

【输出格式】

       第一行两个整数,num和val,表示价值最大的特殊区间的个数以及最大价值。

第二行num个整数,按升序输出每个价值最大的特殊区间的L.

 

【样例输入1】


本以为是个线段树取gcd和min的数据结构乱搞题。但是这样似乎肯定要两个log。目测会tle. 然后想到好久没写的st表似乎还是可以用一用的。。


枚举最小点。向左右分别用二分扩展。st表维护gcd。。。然后就只剩一个log了耶

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define maxn 500010
using namespace std;
int n,a[maxn],f[maxn][20],p[maxn],mxx,mxl,sum,Ans[maxn];
struct node{
    int len,L;
}ans[maxn];
int init(){
    int x=0;char s=getchar();
    while(s<'0'||s>'9')s=getchar();
    while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();}
    return x;
}
int cmp(const node &x,const node &y){
    if(x.len==y.len)return x.L<y.L;
    return x.len>y.len;
}
int Gcd(int a,int b){
    return !b?a:Gcd(b,a%b);
}
int Get_pow(int x){
    for(int i=0;;i++)
        if((1<<i)>x)return i-1;
}
void Get_ST(){
    for(int i=1;i<=n;i++)
        f[i][0]=a[i];
    for(int j=1;j<=18;j++)
        for(int i=1;i+(1<<j)-1<=n;i++)
            f[i][j]=Gcd(f[i][j-1],f[i+(1<<(j-1))][j-1]);
    for(int i=1;i<=n;i++)
        p[i]=Get_pow(i);
}
int find(int l,int r){
    if(l>r)return 0;
    int len=p[r-l+1];
    return Gcd(f[l][len],f[r-(1<<len)+1][len]);
}
int main()
{   n=init();
    for(int i=1;i<=n;i++)
        a[i]=init();
    Get_ST();
    for(int k=1;k<=n;k++){
        int li=k,ri=k,gcd=a[k],l,r;
        l=0;r=n-k+1;
        while(l<=r){
            int mid=l+r>>1;
            int x=find(k,k+mid-1);
            if(x==gcd){
                ri=k+mid-1;l=mid+1;
            }
            else r=mid-1;
        }
        l=0;r=k;
        while(l<=r){
            int mid=l+r>>1;
            int x=find(k-mid+1,k);
            if(x==gcd){
                li=k-mid+1;l=mid+1;
            }
            else r=mid-1;
        }
        ans[k].len=ri-li;ans[k].L=li;
    }
    sort(ans+1,ans+1+n,cmp);
    mxx=ans[1].len;mxl=ans[1].L;
    Ans[++sum]=mxl;
    for(int k=2;k<=n;k++)
        if(ans[k].len!=mxx)break;
        else if(ans[k].L!=mxl){
            Ans[++sum]=ans[k].L;
            mxl=ans[k].L;
        }
    printf("%d %d\n",sum,mxx);
    for(int i=1;i<=sum;i++)
        printf("%d ",Ans[i]);
    return 0;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值