杂 - 数据结构 - 平衡树 - 权值线段树 - JZOJ 100036 随机
2017-08-28 21:31
387 查看
传送门
我居然在考场上想到了正解 = =。对于一个区间,当区间向右扩张时,差的绝对值(可能)会减小,而区间长度会扩大。发现,若差的绝对值已经小于等于区间长度,区间就没有必要继续向右扩张了,而应该考虑尽量把左边界向右靠。因此可以用两个指针来维护区间的左右端点,一直扫过去即可。这个操作的时间复杂度是O(n),想要过这道题,其附属操作的时间复杂度必须是O(logn)。
①一个自然的思路——平衡树(伪)
遇到过不少这样的题,可以用平衡树来维护第一个比自己大和第一个比自己小的数。我们用两棵平衡树来保存需要的数据。第一棵保存当前区间内存在的数,第二棵保存相邻元素的差值。其中,需要对元素进行去重,使第二棵树的最小值不可能是0;再加上一个是否出现过重复元素的标记,就能解决这个问题了。
这种做法还是有点复杂,这里就不多讲了(因为常数太大,过不了),放一个参考代码:
肯定不想看是吧。。。其实不用看,这种东西只适合考场上拿来骗分,不适合二次阅读。
理论上这种算法是可以通过的(本机通过,其它地方打死过不了,看来学校这个机房的电脑强得不行啊!),但是常数可以说是大得惊人的,即使开了O2也没有什么大用处。(其实我在怀疑是不是输入太慢。。。)
②权值线段树
能不能考虑开一棵线段树呢?当然可以(废话 = =)。我们用一棵权值线段树,保存以下信息:
1>该区间出现数的次数
2>该区间差的绝对值的最小值(默认为防溢出型INF)
3>该区间最小的数和最大的数(默认为0)
然后你就惊奇的发现:我能算出来了!。。。为什么可以呢?
当写权值线段树时,请牢记:我是在写权值线段树,线段树上结点的位置是权值!所以当我们合并区间时,当前区间的 差的绝对值的最小值(以下简称答案) 无非有三种情况(分治思想,返璞归真):
1>继承左儿子的答案
2>继承右儿子的答案
3>右儿子中出现过的最小值减去左儿子中出现过的最大值
更新当前区间的最小值和最大值也很简单:
1>如果左儿子有最小值,当前区间的最小值就是左儿子的最小值(请记住这是权值线段树);如果没有,那就是右儿子的最小值;如果还是没有,就不管了。
2>最大值同理,我就不说了。。。
边界情况:
修改时:当区间变成点时,进行对应修改。如果当前位置没有数或者只有一个数,那么答案为INF,否则答案为0。如果当前位置没有数,那么最小值和最大值都为0(NULL),否则为当前位置(记住这是权值线段树)。
查询时:直接查询值域区间的答案即可,即tree[1].ans。
当然,搞事之前离散化一下是最好的,毕竟离散化很好写。鉴于并没有在这里上载过离散化代码,所以还是写一份:
现在解决了技术问题,我们再来看看这道题的思路对应的代码,应该就能一目了然了:
完整线段树代码(手渣,常数巨大,不开O2过不了,还好题目开O2):
这道题再一次展示了线段树的实用性和重要性。
我居然在考场上想到了正解 = =。对于一个区间,当区间向右扩张时,差的绝对值(可能)会减小,而区间长度会扩大。发现,若差的绝对值已经小于等于区间长度,区间就没有必要继续向右扩张了,而应该考虑尽量把左边界向右靠。因此可以用两个指针来维护区间的左右端点,一直扫过去即可。这个操作的时间复杂度是O(n),想要过这道题,其附属操作的时间复杂度必须是O(logn)。
①一个自然的思路——平衡树(伪)
遇到过不少这样的题,可以用平衡树来维护第一个比自己大和第一个比自己小的数。我们用两棵平衡树来保存需要的数据。第一棵保存当前区间内存在的数,第二棵保存相邻元素的差值。其中,需要对元素进行去重,使第二棵树的最小值不可能是0;再加上一个是否出现过重复元素的标记,就能解决这个问题了。
这种做法还是有点复杂,这里就不多讲了(因为常数太大,过不了),放一个参考代码:
const INT maxn = INT(1e6) + 5; INT n; INT A[maxn]; typedef std::map<INT, INT>::iterator Iterator; std::map<INT, INT> num, sub; INT ans = ~(1ll << (sizeof(INT) * 8 - 1)); void run() { n = readIn(); for(int i = 1; i <= n; i++) { A[i] = readIn(); } INT l = 1; INT repeat = 0; num[A[1]]++; for(int i = 2; i <= n; i++) { num[A[i]]++; Iterator it, pre, next, end; pre = next = it = num.find(A[i]); if(pre != num.begin()) pre--; next++; end = num.end(); INT oldSub; INT newSub; INT cntSub; if(it->second == 1) { if(pre != it && next != end) { oldSub = next->first - pre->first; sub[oldSub]--; if(!sub[oldSub]) sub.erase(oldSub); } if(pre != it) { newSub = it->first - pre->first; sub[newSub]++; } if(next != end) { newSub = next->first - it->first; sub[newSub]++; } } else if(it->second == 2) repeat++; if(repeat) cntSub = 0; else cntSub = sub.begin()->first; ans = std::min(ans, std::max(cntSub, i - l + 1)); while(i - l > 1 && cntSub < i - l + 1) { pre = next = it = num.find(A[l]); if(pre != num.begin()) pre--; next++; end = num.end(); l++; if(it->second > 2) it->second--; else if(it->second == 2) { repeat--; it->second--; if(!repeat) { cntSub = sub.begin()->first; } } else { if(pre != it && next != end) { newSub = next->first - pre->first; sub[newSub]++; } if(pre != it) { oldSub = it->first - pre->first; sub[oldSub]--; if(!sub[oldSub]) sub.erase(oldSub); } if(next != end) 4000 { oldSub = next->first - it->first; sub[oldSub]--; if(!sub[oldSub]) sub.erase(oldSub); } num.erase(it); } if(repeat) cntSub = 0; else cntSub = sub.begin()->first; ans = std::min(ans, std::max(cntSub, i - l + 1)); } } cout << ans << endl; }
肯定不想看是吧。。。其实不用看,这种东西只适合考场上拿来骗分,不适合二次阅读。
理论上这种算法是可以通过的(本机通过,其它地方打死过不了,看来学校这个机房的电脑强得不行啊!),但是常数可以说是大得惊人的,即使开了O2也没有什么大用处。(其实我在怀疑是不是输入太慢。。。)
②权值线段树
能不能考虑开一棵线段树呢?当然可以(废话 = =)。我们用一棵权值线段树,保存以下信息:
1>该区间出现数的次数
2>该区间差的绝对值的最小值(默认为防溢出型INF)
3>该区间最小的数和最大的数(默认为0)
然后你就惊奇的发现:我能算出来了!。。。为什么可以呢?
当写权值线段树时,请牢记:我是在写权值线段树,线段树上结点的位置是权值!所以当我们合并区间时,当前区间的 差的绝对值的最小值(以下简称答案) 无非有三种情况(分治思想,返璞归真):
1>继承左儿子的答案
2>继承右儿子的答案
3>右儿子中出现过的最小值减去左儿子中出现过的最大值
更新当前区间的最小值和最大值也很简单:
1>如果左儿子有最小值,当前区间的最小值就是左儿子的最小值(请记住这是权值线段树);如果没有,那就是右儿子的最小值;如果还是没有,就不管了。
2>最大值同理,我就不说了。。。
边界情况:
修改时:当区间变成点时,进行对应修改。如果当前位置没有数或者只有一个数,那么答案为INF,否则答案为0。如果当前位置没有数,那么最小值和最大值都为0(NULL),否则为当前位置(记住这是权值线段树)。
查询时:直接查询值域区间的答案即可,即tree[1].ans。
当然,搞事之前离散化一下是最好的,毕竟离散化很好写。鉴于并没有在这里上载过离散化代码,所以还是写一份:
const INT maxn = INT(1e6) + 5; INT n, N; INT A[maxn]; INT disc[maxn]; void discretize() //之后获取原值的方法:disc[A[i]] { for(int i = 1; i <= n; i++) { disc[i] = A[i]; } std::sort(disc + 1, disc + 1 + n); N = std::unique(disc + 1, disc + 1 + n) - (disc + 1); //下标从1开始 for(int i = 1; i <= n; i++) { A[i] = std::lower_bound(disc + 1, disc + 1 + N, A[i]) - disc; //注意与上面不一样,因为上面是为了获得个数,而这里是为了从1开始 } }
现在解决了技术问题,我们再来看看这道题的思路对应的代码,应该就能一目了然了:
discretize(); INT l = 1; st.insert(A[1]); for(int i = 2; i <= n; i++) { st.insert(A[i]); INT cntSub = st.query(); ans = std::min(ans, std::max(cntSub, i - l + 1)); while(i - l > 1 && cntSub < i - l + 1) { st.erase(A[l]); l++; cntSub = st.query(); ans = std::min(ans, std::max(cntSub, i - l + 1)); } } cout << ans << endl;
完整线段树代码(手渣,常数巨大,不开O2过不了,还好题目开O2):
class SegTree { #define PARAM INT node = 1, INT l = 1, INT r = N #define DEF INT mid = (l + r) >> 1; INT lc = node << 1; INT rc = lc | 1; struct Node { INT minPos; INT maxPos; INT minSub; INT contain; Node() : minSub(INF >> 1) {} //为了防止溢出除以一个2 } tree[maxn * 4]; INT g_Pos, g_Val; void update(PARAM) //请与上面的叙述对应 { DEF; tree[node].minSub = std::min(tree[lc].minSub, tree[rc].minSub); if(tree[lc].maxPos && tree[rc].minPos) tree[node].minSub = std::min(tree[node].minSub, disc[tree[rc].minPos] - disc[tree[lc].maxPos]); tree[node].minPos = tree[lc].minPos; if(!tree[node].minPos) tree[node].minPos = tree[rc].minPos; tree[node].maxPos = tree[rc].maxPos; if(!tree[node].maxPos) tree[node].maxPos = tree[lc].maxPos; } void add_(PARAM) { if(l == r) { tree[node].contain += g_Val; if(tree[node].contain) tree[node].minPos = tree[node].maxPos = l; else tree[node].minPos = tree[node].maxPos = 0; if(tree[node].contain >= 2) tree[node].minSub = 0; else tree[node].minSub = INF; return; } DEF; if(g_Pos <= mid) add_(lc, l, mid); if(g_Pos > mid) add_(rc, mid + 1, r); update(node, l, r); } public: INT query() { return tree[1].minSub; } void insert(INT x) { g_Pos = x; g_Val = 1; add_(); } void erase(INT x) { g_Pos = x; g_Val = -1; add_(); } } st;
这道题再一次展示了线段树的实用性和重要性。
相关文章推荐
- JZOJ 100036 【NOIP2017提高A组模拟7.10】随机
- 【权值线段树】bzoj3224 Tyvj 1728 普通平衡树
- 数据结构---平衡树
- Luogu P1637 三元上升子序列【权值线段树】By cellur925
- poj 2828 Buy Tickets(数据结构:线段树)
- bzoj 2141 线段树套权值线段树
- |poj 2299|权值线段树|Ultra-QuickSort
- hdu2665 poj2104 可持久化数据结构之线段树
- 数据结构--线段树--lazy延迟操作
- 【bzoj3196】二逼平衡树【树套树】【线段树】【平衡树】【呵呵】
- 随机种子数据结构 - Random
- 【BZOJ3110】【codevs1616】K大数查询,权值线段树套普通线段树
- POJ 2828 Buy Tickets [线段树-单点更新]【数据结构】【好题】
- HDU 3308 LCIS 最长连续递增序列+数据结构+线段树+单点更新
- STL 平衡树数据结构容器
- 【HIHOCODER 1576】 子树中的最小权值(线段树维护DFS序)
- 数据结构:线段树(树状数组、BST、LCA、
- 【数据结构】线段树总结(一)
- [jzoj]3499. 【NOIP2013模拟联考15】人类基因组(genes) (单调队列、前缀和、线段树解一题)
- Codeforces 542A. Place Your Ad Here (扫描线进阶 带权值的线段交求最大值) (线段树)