[Offer收割]编程练习赛51- C 等差子数列

题目3 : 等差子数列


很久没做题手生了许多。
题意:中文题。
思路:关键在于连续。一个数要么和前两个构成等差,长度加一;要么和前一个构成新的等差,长度为2,前提是前一个存在。那么我们可以写出每个数的位置所能达到的最长的等差连续子序列。
如:

    1 2 3 5 7 9

对应:1 2 3 2 3 4

这样貌似只需查询区间最值即可,但切勿忽略一个问题:如果上述数列查询的区间为[2,4],ans应为2而不是3。这种情况id=3的位置实际贡献应该是2.

假设区间某一位置id的连续最长等差子序列长度为ma:

① ma<=id-l+1 : ans=ma(此序列完全包含在所查询区间内)

② ma>id-l+1 : ans=id-l+1 ???这个ans只能代表前面一小段的贡献,而后面的可能有更长的。

比如: 1 2 3 4 1 2 3,查询区间[3,7],正确答案应该是3.

所以在②的情况下,还需要查询后面一小段的最大值即可,这一小段能保证全在查询区间内。然后ANS=max(id-l+1,post_max).

我们在查询的时候需要知道最大值及其位置。也就是很简单的线段树,但很久没写代码了调了半天。

//头文件已删
#define sc(x) scanf("%d",&x)
#define pd(x) printf("%d\n",x)
#define cls(a,x) memset(a,x,sizeof(a))
const double eps=1e-8;
const double PI=acos(-1.0);
const int INF=1e9+10;
const int mod=1e9+7;
const int N=2e5+10;
int c[N],b[N],n,m;
//char s[N];
struct node
{
    int l,r;
    int ma,id;
}a[N<<2];
void build(int l,int r,int k)
{
    a[k].l=l,a[k].r=r,a[k].ma=0,a[k].id=0;
    if(l==r) return ;
    int mid=(l+r)>>1;
    build(l,mid,2*k);
    build(mid+1,r,2*k+1);
}
void in(int pos,int num,int k)
{
    if(a[k].l==a[k].r&&a[k].l==pos)
    {
        a[k].ma=num;
        a[k].id=pos;
        return ;
    }
    int mid=(a[k].l+a[k].r)>>1;
    if(pos<=mid) in(pos,num,2*k);
    else in(pos,num,2*k+1);
    if(a[k*2+1].ma>=a[k*2].ma) a[k].ma=a[k*2+1].ma,a[k].id=a[k*2+1].id;
    else a[k].ma=a[k*2].ma,a[k].id=a[k*2].id;
}
int id,maxx;//得到最大值,与最大值的位置
void Q1(int l,int r,int k)
{
    if(l>r) return ;
    if(a[k].l==l&&a[k].r==r)
    {
//      printf("l=%d r=%d  maxx=%d id=%d\n",l,r,a[k].ma,a[k].id);
        if(a[k].ma>maxx)
        {
            maxx=a[k].ma;
            id=a[k].id;
        }
        else if(a[k].ma==maxx&&a[k].id>id) id=a[k].id;
        return ;
    }
    int mid=(a[k].l+a[k].r)>>1;
    if(r<=mid) Q1(l,r,2*k);
    else if(l>mid) Q1(l,r,2*k+1);
    else
    {
        Q1(l,mid,2*k);
        Q1(mid+1,r,2*k+1);
    }
}
int main()
{
    while(~sc(n))
    {
        sc(m);
        build(1,n,1);
        b[0]=0;
        int la=0;
        for(int i=1; i<=n; i++)
        {
            sc(c[i]);
            if(i<=2)
            {
                if(i==2) la=c[i]-c[i-1];
                b[i]=i;
            }
            else
            {
                if(c[i]-c[i-1]==la) b[i]=max(2,b[i-1]+1);
                else la=c[i]-c[i-1],b[i]=2;
            }
        }
        for(int i=1; i<=n; i++)in(i,b[i],1);// printf("%d%c",b[i],i==n?'\n':' '),
        int l,r;
        while(m--)
        {
            sc(l);
            sc(r);
            if(l>r) swap(l,r);
            if(r-l+1<=2) printf("%d\n",r-l+1);
            else
            {
                maxx=0,id=0;//先求出最大值及其位置
                Q1(l,r,1);
//                printf("ma=%d id=%d\n",maxx,id);
                if(maxx<=id-l+1) printf("%d\n",maxx);
                else
                {
                    int now_ma=id-l+1;//只能有这么多的贡献
                    maxx=0;
//                    printf("id+1=%d r=%d\n",id+1,r);
                    Q1(id+1,r,1);
                    printf("%d\n",max(now_ma,maxx));
                }
            }
        }
    }
}
//6 2
//1 2 3 5 7 9
//2 6
//1 4
//
//7 8
//1 2 3 4 1 2 3
//2 6


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值