ACM学习历程—HDU5696 区间的价值(分治 && RMQ && 线段树 && 动态规划)

http://acm.hdu.edu.cn/showproblem.php?pid=5696

这是这次百度之星初赛2B的第一题,但是由于正好打省赛,于是便错过了。加上2A的时候差了一题,当时有思路,但是代码就是过不去。。这次应该是无缘复赛了。。

先不水了,省赛回来,我看了一下这个题,当时有个类似于快排的想法,今天试了一下,勉强AC了。。跑了3S多。

思路就是我枚举区间左值lt,那么[lt, n]区间内最值的角标分别为mima。于是设to = max(mi, ma)。也就是说在to右侧的所有区间[lt, i]的值至少都是a[mi]*a[ma]。用线段树维护长度为i区间的最值,那么我需要用a[mi]*a[ma]去更新区间[to-lt+1, rt-lt+1]在线段树中的值。然后区间就可以缩减为[lt, to-1]了,于是递归求解就可以了,当然此处可以迭代。

关键是上述的递归过程最多需要运行多少次?

首先to这个位置,要么是mi,要么是ma,也就是说左侧的数据要么都比to这个位置的数小,要么都比它大。光看左侧,这个to很像快排一次运行的那个分隔值。那么to平均下来应该是(lt+rt)/2

那么总的复杂度就是nlognlogn.

但是此处线段树常数较大,所以需要减一下枝,就是当更新值pls比子树中任意值都小,就可以不用更新,维护子树的最小值就可以了。

 

代码:

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <set>
#include <map>
#include <queue>
#include <vector>
#include <string>
#define LL long long

using namespace std;

const int maxN = 100005;
int n, a[maxN];

//RMQ-ST算法
//效率nlogn
//查询区间最值,注意区间[0, n-1]和[1, n]的区别
int ma[maxN][20], mi[maxN][20];

void RMQ()
{
    memset(ma, 0, sizeof(ma));
    memset(mi, 0, sizeof(mi));
    for (int i = 1; i <= n; ++i)
        mi[i][0] = ma[i][0] = i;
    for (int j = 1; (1<<j) <= n; ++j)
        for (int i = 1; i+(1<<j)-1 <= n; ++i)
        {
            if (a[ma[i][j-1]] >= a[ma[i+(1<<(j-1))][j-1]])
                ma[i][j] = ma[i][j-1];
            else
                ma[i][j] = ma[i+(1<<(j-1))][j-1];
            if (a[mi[i][j-1]] <= a[mi[i+(1<<(j-1))][j-1]])
                mi[i][j] = mi[i][j-1];
            else
                mi[i][j] = mi[i+(1<<(j-1))][j-1];
        }
}

int queryMax(int lt, int rt)
{
    int k = 0;
    while ((1<<(k+1)) <= rt-lt+1)
        k++;
    if (a[ma[lt][k]] >= a[ma[rt-(1<<k)+1][k]])
        return ma[lt][k];
    else
        return ma[rt-(1<<k)+1][k];
}

int queryMin(int lt, int rt)
{
    int k = 0;
    while ((1<<(k+1)) <= rt-lt+1)
        k++;
    if (a[mi[lt][k]] <= a[mi[rt-(1<<k)+1][k]])
        return mi[lt][k];
    else
        return mi[rt-(1<<k)+1][k];
}

//线段树
//求区间最值
struct node
{
    int lt, rt;
    LL val, delta;
}tree[4*maxN];

//向下更新
void pushDown(int id)
{
    if (tree[id].delta != 0)
    {
        tree[id<<1].val = tree[id<<1].delta = max(tree[id<<1].val, tree[id].delta);
        tree[id<<1|1].val = tree[id<<1|1].delta = max(tree[id<<1|1].val, tree[id].delta);
        tree[id].delta = 0;
    }
}

//向上更新
void pushUp(int id)
{
    tree[id].val = min(tree[id<<1].val, tree[id<<1|1].val);
}

//建立线段树
void build(int lt, int rt, int id)
{
    tree[id].lt = lt;
    tree[id].rt = rt;
    tree[id].val = 0;//每段的初值,根据题目要求
    tree[id].delta = 0;
    if (lt == rt)
    {
        //tree[id].delta = ??;
        return;
    }
    int mid = (lt+rt)>>1;
    build(lt, mid, id<<1);
    build(mid+1, rt, id<<1|1);
}

//增加区间内每个点固定的值
void change(int lt, int rt, int id, LL pls)
{
    if (pls <= tree[id].val) return;
    if (lt <= tree[id].lt && rt >= tree[id].rt)
    {
        tree[id].val = tree[id].delta = max(tree[id].delta, pls);
        return;
    }
    pushDown(id);
    int mid = (tree[id].lt+tree[id].rt)>>1;
    if (lt <= mid)
        change(lt, rt, id<<1, pls);
    if (rt > mid)
        change(lt, rt, id<<1|1, pls);
    pushUp(id);
}

//查询某段区间内的最值
LL query(int lt, int rt, int id)
{
    if (lt <= tree[id].lt && rt >= tree[id].rt)
        return tree[id].val;
    pushDown(id);
    int mid = (tree[id].lt+tree[id].rt)>>1;
    if (rt <= mid)
        return query(lt, rt, id<<1);
    if (lt > mid)
        return query(lt, rt, id<<1|1);
    return max(query(lt, mid, id<<1), query(mid+1, rt, id<<1|1));
}

void input()
{
    for (int i = 1; i <= n; ++i) scanf("%d", &a[i]);
    RMQ();
    build(1, n, 1);
}

int cnt;

void cal(int lt, int rt)
{
    int to, mi, ma;
    while (lt <= rt)
    {
        mi = queryMin(lt, rt);
        ma = queryMax(lt, rt);
        to = max(mi, ma);
        change(to-lt+1, rt-lt+1, 1, (LL)a[mi]*a[ma]);
        rt = to-1;
    }
}

void work()
{
    cnt = 0;
    for (int i = 1; i <= n; ++i)
        cal(i, n);
    for (int i = 1; i <= n; ++i)
        printf("%lld\n", query(i, i, 1));
}

int main()
{
    //freopen("test.out", "w", stdout);
    //freopen("test.in", "r", stdin);
    while (scanf("%d", &n) != EOF)
    {
        input();
        work();
    }
    return 0;
}
View Code

 

posted on 2016-05-24 13:25  AndyQsmart  阅读(630)  评论(0编辑  收藏  举报

导航