Breif description:
There is a sequence a of length n. We use ai to denote the i-th element in this sequence. You should do the following three types of operations to this sequence.
0 x y t: For every x≤i≤y, we use min(ai,t) to replace the original ai’s value.(区间取 min)1 x y: Print the maximum value of ai that x≤i≤y.(区间求最大値)2 x y: Print the sum of ai that x≤i≤y.(区间求和)
Analysis:
- http://acm.hdu.edu.cn/showproblem.php?pid=5306
- http://codeforces.com/contest/444/problem/C
- http://codeforces.com/contest/453/problem/E
这类线段树问题的标记操作比一般的要复杂,需要使用均摊分析。
首先尝试写一个 暴力的线段树。
...
void upd(int x, int l, int r){ //上传信息
T[x].ss = T[lx].ss + T[rx].ss;
T[x].mx = max(T[lx].mx, T[rx].mx);
}
void pnc(int x, int l, int r, int t){ //打标记
if (T[x].mx <= t) return;
<pre><code>T[x].tag = t;
if (l &lt; r){
pnc(lx, l, ml, t); pnc(rx, mr, r, t);
upd(xx);
}
else{
T[x].ss = T[x].mx = t;
}
</code></pre>
}
void rls(int x, int l, int r){ //下放标记,因为我们是暴力打标记的。。所以暂时不需要。。
//pnc(lc, T[x].tag); pnc(rc, T[x].tag);
}
我们发现主要瓶颈是打标记这个步骤,因为在标记作用上的时刻,我们不知道它的子树中,有多少节点将会被这个标记影响。
因此需要递归到子树中去。。。
注意到这题中我们的 tag 标记是单调的,只需要留意子树中更小的标记。
4 4 2 6 -> 2 - 1 3 5 7 1 -
虽然这样第一行加了剪枝,然而并没有什么卵用。最坏情况下,递减的修改根节点,每次我们需要递归整棵树,复杂度 O(n),和直接用数组暴力的复杂度一样。。。
。。。回忆 http://codeforces.com/contest/444/problem/C 。。。
我们注意到,在这种情况下,所有节点的 tag 信息总是相同的,我们理应利用这个信息,不要每次递归到整个子树中去。
而这要求我们多维护一个额外信息。。。
struct rec{
LL ss; int mx, cv;
int tag;
} T[N];
这里 cv 表示,这个区间中有多少个结点,已被子树中的 tag 影响。
我们有:
void upd(int x, int l, int r){
T[x].ss = T[lx].ss + T[rx].ss;
T[x].mx = max(T[lx].mx, T[rx].mx);
T[x].cv = T[lx].cv + T[rx].cv;
}
void pnc(int x, int l, int r, int t){
if (T[x].tag != Dummy && T[x].tag <= t) return;
<pre><code>T[x].tag = t;
if (T[x].cv != r-l+1){
T[x].mx = t;
T[x].ss += (LL) t * ((r-l+1)-T[x].cv);
T[x].cv = r-l+1;
}
</code></pre>
}
void rls(int x, int l, int r){
if (T[x].tag != Dummy){
pnc(lc, T[x].tag); pnc(rc, T[x].tag);
}
}
。。。这样还是我们熟悉的普通线段树的写法。。。复杂度是对的。。但是问题在于,在打标记的时候,我们其实并不能直接维护 cv。
因此在每次修改的时候,我们还需要添加一个 fix 函数,清理子树中所有大于等于 t 的标记。
void Insert(int x, int l, int r){
<pre><code>if (T[x].mx &lt;= t) return;
if (b &lt; l || r &lt; a) return;
if (a &lt;= l &amp;&amp; r &lt;= b){
fix(xx, t);
if (l == r){
T[x].ss = T[x].mx = T[x].tag = t;
T[x].cv = 1;
}
else{
upd(xx);
}
pnc(xx, t);
}
else{
rls(xx);
Insert(lc); Insert(rc);
upd(xx);
}
</code></pre>
}
。。。这样我们还剩 fix 函数需要考察。。。
void fix(int x, int l, int r, int t){
<pre><code>if (T[x].mx &lt; t) return;
if (T[x].tag &gt;= t){
T[x].tag = Dummy;
}
if (l == r){
T[x].ss = T[x].mx = T[x].tag = T[x].tag;
T[x].cv = T[x].tag != Dummy;
}
else{
rls(xx);
fix(lc, t); fix(rc, t);
upd(xx);
}
</code></pre>
}
。。。此时它已经和我们最初的方法有了很大的不同。。。。。
初看起来,每次 Insert 操作我们可能调用 O(log(n)) 次 fix 函数,每次 fix 的复杂度又可能高达 O(n)。
我们对其进行均摊分析!
当一个标记被清理成 Dummy 时,其子树中所有标记也都被赋成 Dummy,并且它的 mx = 0
我们考察最上面的那些 Dummy 标记,称之为 封闭节点。
我们设置 封闭节点 的 mx 値等于 0,因而它们不会参与到 fix 函数的递归中去。
Insert 操作,最多可能形成 O(n) 个 封闭节点。
Insert 操作,最多只会打开 O(logn) 个 封闭节点。
因此总的时间复杂度仍为 O(nlogn)。-> 完整代码




Alca
Amber
Belleve Invis
Chensiting123
Edward_mj
Fotile96
Hlworld
Kuangbin
Liyaos
Lwins
LYPenny
Mato 完整版
Mikeni2006
Mzry
Nagatsuki
Neko13
Oneplus
Rukata
Seter
Sevenkplus
Sevenzero
Shirleycrow
Vfleaking
wangzhpp
Watashi
WJMZBMR
Wywcgs
XadillaX
Yangzhe
三途川玉子
About.me
Vijos
