cf 474E 线段树+dp+离散化后二分查找

题意:给你n个柱子,每个柱子都有一个高度,你从第一个柱子开始跳往后跳,后面的柱子要满足|hi-hj|>=d才可以跳上去,问最多跳多少步,并且输出一种跳法,从小到大输出柱子号。
思路:真肯定是个dp,而且思路很简单//转移方程 dp[i]=max(dp[j])+1,(|hi-hj|>=d)
但是n的范围是10^5,那么你每次都对之前的柱子扫一边找最大就不行了,那样就是n^2的算法,所以要对每次找最大进行优化,这里优化的方式就是应用线段树,而输出路径就用递归就行,由于没有怎么输出过路径导致不太会。
线段树优化的道理与正确性:
一开始不知道怎么优化,光想着对下标建树,怎么想怎么不对,后来找了一份别人的代码一看原来是对高度建树,因为你要找的是符合这个条件 (|hi-hj|>=d)的dp[j],所以你可以把所有i之前的dp[j]都存在相应高度的线段树端点处(正因为这个才更应该用线段树),然后每次query(h[i]+d~maxn )与(1~h[i]-d)的最大的dp[j],然后再来一次单点更新,把a[i]点的值改为dp[j]+1. 另外需要考虑的机试h的范围为10^15,你直接建线段树肯定是建不下的,所以要对高度进行离散化,然后对离散化后的高度个数建树,然后对每个a[i]进行二分查找,找到它在离散化数组中的下标,而且还有小于h[i]-d的第一个数的下标与大于h[i]+d第一个数的小标,都要用二分查找,具体怎么写的看代码。
#include<iostream>
#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<stdlib.h>
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
#define ll long long
using namespace std;
const int maxn=100010;
//转移方程 dp[i]=max(dp[j])+1,(|hi-hj|>=d)

ll mx[maxn<<2],pos[maxn<<2],pre[maxn];
ll a[maxn],b[maxn];
ll n,d;
int cur;
ll maxlen,maxpos;
ll best;

void pushup(int rt)
{
    if(mx[rt<<1]>mx[rt<<1|1])
    {
        mx[rt]=mx[rt<<1];
        pos[rt]=pos[rt<<1];
    }
    else
    {
        mx[rt]=mx[rt<<1|1];
        pos[rt]=pos[rt<<1|1];
    }
}

void update(int q,int c,int i,int l,int r,int rt)
{
    if(l==r)
    {
        if(mx[rt]<c)
        {
            mx[rt]=c;
            pos[rt]=i;
        }
        return;
    }
    int mid=(l+r)>>1;
    if(q<=mid) update(q,c,i,lson);
    else update(q,c,i,rson);
    pushup(rt);
}

void query(int L,int R,int l,int r,int rt)
{
    if(L<=l&&r<=R)
    {
        if(mx[rt]>maxlen)
        {
            maxlen=mx[rt];
            maxpos=pos[rt];
        }
        return;
    }
    int mid=(l+r)>>1;
    if(L<=mid) query(L,R,lson);
    if(mid<R) query(L,R,rson);
}

void print(int m)
{
    if(pre[m])
    {
        print(pre[m]);
        printf("%d ",pre[m]);
    }
}

void init()
{
    memset(mx,0,sizeof(mx));
    memset(pos,0,sizeof(pos));
    memset(pre,0,sizeof(pre));
    cur=1;
}

int findl(long long num) {//查找比num小的第一个值,因为num不一定在b这个数组里
    int l = 1, r = cur;
    while(l < r) {
        int mid = (l + r + 1) / 2;
        if (b[mid] <= num) {
            l = mid;
        } else {
            r = mid - 1;
        }
    }
    return r;
}

int findr(long long num) {//查找比num大的第一个值
    int l = 1, r = cur;
    while(l < r) {
        int mid = (l + r) / 2;
        if (b[mid] >= num) {
            r = mid;
        } else {
            l = mid + 1;
        }
    }
    return l;
}


int main()
{
    cin>>n>>d;
    init();
    for(int i=1; i<=n; i++)
    {
        cin>>a[i];
        b[i]=a[i];
    }
    sort(b+1,b+n+1);
    for(int i=2; i<=n; i++)
    {
        if(b[i]!=b[i-1])
        {
            b[++cur]=b[i];
        }
    }
    update(findl(a[1]),1,1,1,cur,1);
    int best=1;
    int len=1;
    pre[1]=0;
    for(int i=2; i<=n; i++)
    {
        maxlen=0; maxpos=0;
        int L=findl(a[i]-d);
        int R=findr(a[i]+d);
//        cout<<L<<"  "<<R<<endl;
        if(b[L]+d<=a[i]){
            query(1,L,1,cur,1);
        }
        if(a[i]+d<=b[R]){
            query(R,cur,1,cur,1);
        }
        update(findl(a[i]),maxlen+1,i,1,cur,1);
//        cout<<maxpos<<"  "<<maxlen<<endl;
        pre[i] = maxpos;
        if(maxlen+ 1>len)
        {
            len = maxlen+ 1;
            best= i;
        }
    }
    printf("%d\n",len);
    print(best);
    cout<<best<<endl;
    return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值