【WinterCamp 2013】楼房重建(线段树维护动态单调栈)

Problem

  • 题意就是求一个支持修改的单调栈长度.

  • 修改次数 m m m,序列长度 n n n.

Data constraint

  • n , m ≤ 100000 n,m\le 100000 n,m100000

Solution

  • 这是一个经典模型.

  • 这个是用线段树来实现的,先不妨假设现在要求上升子序列长度.

  • 那么如果我们要修改,则直接在线段树上找到对应位置,然后进行修改即可.

  • 对于线段树每个节点,维护一个 A n s Ans Ans(这个区间单调栈的长度),一个 v v v(这个区间内数的最大值)

  • 现在,当只有最左边一个数时,单调栈长度显然为 1 1 1.

  • 那么归纳,假设左边的单调栈已经构好,现在需要计算当前节点的答案.

  • 那么显然 当前答案 = 左边的答案 + 左边最大的数放在 M + 1 ∼ e n M+1\sim en M+1en这段区间内得到的单调栈长度

  • 而形如 X = Y + Z X=Y+Z X=Y+Z的这个式子,右边的 Z Z Z依旧可以 l o g log log的时间复杂度内计算出来,做法类似.

  • 不妨设 f ( x , y , z ) f(x,y,z) f(x,y,z)表示在 [ x , y ] [x,y] [x,y]这段闭区间形成的单调栈栈顶加入 z z z这个点形成的新单调栈长度, L s , R s Ls,Rs Ls,Rs分别表示左右儿子, n o w now now表示当前点.

  • 那么依旧是讨论,假设左区间 [ x , M ] [x,M] [x,M]的最大值小于等于插入的数,那么贡献显然是 f ( M + 1 , e n , z ) f(M+1,en,z) f(M+1,en,z)

  • 否则贡献是 ( t r [ n o w ] . v − t r [ L s ] . v ) + f ( s t , M , v ) (tr[now].v-tr[Ls].v) + f(st,M,v) (tr[now].vtr[Ls].v)+f(st,M,v),这里是整个算法最巧妙的一处.

  • 因为整个过程是在线段树上进行的,所以当计算到 x x x这个点的答案时,其所有儿子的答案都已经计算完毕了.

  • 那么因为 t r [ n o w ] . v = t r [ L s ] . v + f ( M + 1 , e n , t r [ L s ] . v ) tr[now].v=tr[Ls].v+f(M+1,en,tr[Ls].v) tr[now].v=tr[Ls].v+f(M+1,en,tr[Ls].v),所以实际上 f ( M + 1 , e n , t r [ L s ] . v ) f(M+1,en,tr[Ls].v) f(M+1,en,tr[Ls].v)是等价于 t r [ n o w ] . v − t r [ L s ] . v tr[now].v-tr[Ls].v tr[now].vtr[Ls].v.

  • 所以整个算法就是 O ( n l o g 2 n ) O(nlog^2n) O(nlog2n)的。

#include 

#define F(i, a, b) for (int i = a; i <= b; i ++)
#define max(a, b) ((a) > (b) ? (a) : (b))
#define M (st + en >> 1)
#define Ls (x << 1)
#define Rs (Ls | 1)

using namespace std;

int n, m, X, Y;
struct node { double v; int ans; } F[400000];

int Calc(int x, int st, int en, double v) {
	if (st == en)
		return F[x].v > v;
	else
		return F[Ls].v <= v ? Calc(Rs, M + 1, en, v) : F[x].ans - F[Ls].ans + Calc(Ls, st, M, v);
}

void Make(int x, int st, int en, int p, double v) {
	if (st == en)
		F[x] = {v, 1};
	else
		M >= p ? Make(Ls, st, M, p, v) : Make(Rs, M + 1, en, p, v),
		F[x].v = max(F[Ls].v, F[Rs].v), F[x].ans = F[Ls].ans + Calc(Rs, M + 1, en, F[Ls].v);
}

int main() {
	scanf("%d%d", &n, &m);
	F(i, 1, m)
		scanf("%d%d", &X, &Y),
		Make(1, 1, n, X, (double) Y / X),
		printf("%d\n", F[1].ans);
}

你可能感兴趣的:(线段树)