51Nod - 1153 选择子序列 (单调栈+线段树)

题目链接:https://www.51nod.com/onlineJudge/questionCode.html#!problemId=1153点击打开链接

题目来源:  Codility
基准时间限制:1 秒 空间限制:131072 KB 分值: 40  难度:4级算法题
长度为N的整数数组A,所有的数均不相同,假设下标从0开始。找到一个最长的数组B,B数组的长度为K,数值范围是0 - N - 1,记录的是A数组的下标。满足A[B[0]] > A[B[1]] > A[B[2]] >...A[B[K]],并且对任意连续的两项B[i]及B[i + 1],满足min(B[i],B[i + 1]) < j < max(B[i],B[i + 1]) 均有A[j] < A[B[i + 1]] ,求最大的K。例如:9, 10, 2, -1, 3, -5, 0, -3, 1, 12, 5, 8, -2, 6, 4。可以选出:12, 10, 3, 1, 0, -3。对应的下标为:9, 1, 4, 8, 6, 7(就是B数组),输出6。
Input
第1行:一个数N,表示A数组的长度。(1 <= N <= 50000)
第2 - N + 1行:每行1个数对应A数组的元素Ai(0 < Ai < 10^9)
Output
输出B数组最长的长度K。
Input示例
15
9
10
2
-1
3
-5
0
-3
1
12
5
8
-2
6
4
Output示例
6


这道题网上的写法很多 单调栈 线段树 dfs。。

一开始想的是单调栈+贪心 单调栈左右扫一遍之后每次选取符合条件且范围最大的加入 但是存在像 1 2 10 3 4 12 9 8 7 6这样的数据 贪心会导致错误的选择

然后因为跟区间有关因此想到线段树 

从小到大依次将每个数的范围的maxx值+1 维护每个区间的最大值

之所以从小到大 这样保证了后面加入的数一定能够包含比他小的数 并且选取的都是最优的路径+1

细想其实不难


#include <bits/stdc++.h>
using namespace std;
int a[50010];
int la[50010];
int ra[50010];
struct xjy
{
    int sum;
    int num;
    bool operator < (const xjy &r)const
    {
        return  sum > r.sum;
    }
};
struct xjyy
{
    int left;
    int right;
    int maxx;
};
xjyy tree[50010<<2];
void build(int i,int left,int right)
{
    if(left==right)
    {
        tree[i].left=left;
        tree[i].right=right;
        tree[i].maxx=0;
        return;
    }
    int mid=(left+right)>>1;
    build(i<<1,left,mid);
    build(i<<1|1,mid+1,right);
    tree[i].left=left;
    tree[i].right=right;
    tree[i].maxx=0;
}

void update(int i,int left,int right)
{
    if(left==tree[i].left&&tree[i].right==right)
    {
        tree[i].maxx++;
        return;
    }
    int mid=(tree[i].left+tree[i].right)>>1;
    if(right<=mid)
        update(i<<1,left,right);
    else if(left>mid)
        update(i<<1|1,left,right);
    else
    {
        update(i<<1,left,mid);
        update(i<<1|1,mid+1,right);
    }
    tree[i].maxx=max(tree[i<<1].maxx,tree[i<<1|1].maxx);
}

priority_queue<xjy > q;
int main()
{
    int n;
    cin >> n;
    for(int i=1;i<=n;i++)
        scanf("%d",&a[i]);
    stack <int > s;
    for(int i=1;i<=n;i++)
    {
        la[i]=i;
        if(s.empty())
        {
            s.push(i);
            la[i]=1;
        }
        else
        {
            while(!s.empty())
            {
                if(a[s.top()]<a[i])
                {
                    la[i]=la[s.top()];
                    s.pop();

                }
                else
                {
                    s.push(i);
                    break;
                }
            }
            if(s.empty())
                s.push(i);
        }
    }
    while(!s.empty())
        s.pop();


    for(int i=n;i>=1;i--)
    {
        ra[i]=i;
        if(s.empty())
        {
            s.push(i);
            ra[i]=i;
        }
        else
        {
            while(!s.empty())
            {
                if(a[s.top()]<a[i])
                {
                    ra[i]=ra[s.top()];
                    s.pop();
                }
                else
                {
                    s.push(i);
                    break;
                }
            }
            if(s.empty())
                s.push(i);
        }
    }
    build(1,1,n);
    for(int i=1;i<=n;i++)
        {
            xjy mid;
            mid.num=i;
            mid.sum=a[i];
            q.push(mid);
        }
    while(!q.empty())
    {
        xjy mid=q.top();
        q.pop();
        update(1,la[mid.num],ra[mid.num]);
    }
    cout << tree[1].maxx;
}





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值