ACM学习历程—HDU5696 区间的价值(分治 && RMQ && 线段树 && 动态规划)
Posted AndyQsmart
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ACM学习历程—HDU5696 区间的价值(分治 && RMQ && 线段树 && 动态规划)相关的知识,希望对你有一定的参考价值。
http://acm.hdu.edu.cn/showproblem.php?pid=5696
这是这次百度之星初赛2B的第一题,但是由于正好打省赛,于是便错过了。加上2A的时候差了一题,当时有思路,但是代码就是过不去。。这次应该是无缘复赛了。。
先不水了,省赛回来,我看了一下这个题,当时有个类似于快排的想法,今天试了一下,勉强AC了。。跑了3S多。
思路就是我枚举区间左值lt,那么[lt, n]区间内最值的角标分别为mi和ma。于是设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; }
以上是关于ACM学习历程—HDU5696 区间的价值(分治 && RMQ && 线段树 && 动态规划)的主要内容,如果未能解决你的问题,请参考以下文章