数据结构 100 题 1~10 线段树

T1 无聊的数列

来自:Link

flag 帖先从水题入手。

首先分析题目,它是以等差数列为原型进行的修改。等差数列一大性质就是其差分数列的值除第一项以外均相等。

于是不难想到使用差分数列进行维护。

假设原数组为 (A),其差分数列为 (num)。规定 (num_i = A_i - A_{i - 1}(i in [1, n]))。 当前更改区间 (l, r)。需累加的等差数列首项为 (k),公差为 (d),长度为 (r - l + 1)

对于 (l),我们需要将 (A_l) 加上 (k),即是把 (num_l) 加上 (k)

对于 (l + 1)(r),我们需要将 (A_j(j in [l + 1, r])) 加上 (d imes (j - l)),即是把 (num_j(j in [l + 1, r])) 加上 (d)

总所周知,(A_i = sum^n_{i = 1} num_i),而我们更改了 (num_j(j in [l + 1, r]),为了保证 (A_j(j in [min(r + 1, n), n])) 不改变,则需要将 (num_{r + 1}) 这一项减去前面累加的值。即把 (num_{r + 1}) 减去 (k + (r - (l + 1) + 1) imes d)。注意 (r + 1) 可能越界,建树时因多建一位。

区间维护即可。

#include <cstdio>
#define lson p << 1
#define rson p << 1 | 1

int read() {
    int k = 1, x = 0;
    char s = getchar();
    while (s < '0' || s > '9') {
        if (s == '-')
            k = -1;
        s = getchar();
    }
    while (s >= '0' && s <= '9') {
        x = (x << 3) + (x << 1) + s - '0';
        s = getchar();
    }
    return x * k;
}

void write(int x) {
    if(x < 0) {
    	putchar('-');
		x = -x;
    }
    if(x > 9)
		write(x / 10);
    putchar(x % 10 + '0');
}

void print(int x, char s) {
	write(x);
	putchar(s);
}

const int MAXN = 1e5 + 5;
const int MAXT = MAXN * 4 + 5;

struct Segment_Tree {
	int l, r, x, add;
	Segment_Tree() {}
	Segment_Tree(int L, int R, int X, int Add) {
		l = L;
		r = R;
		x = X;
		add = Add;
	}
} t[MAXT];

int num[MAXN];

void Push_Up(int p) {
	t[p].x = t[lson].x + t[rson].x;
}

void Make_Tree(int p, int l, int r) {
	t[p].l = l;
	t[p].r = r;
	if(l == r) {
		t[p].add = 0;
		t[p].x = num[l];
		return ;
	}
	int mid = (l + r) >> 1;
	Make_Tree(lson, l, mid);
	Make_Tree(rson, mid + 1, r);
	Push_Up(p);
}

void Spread(int p) {
	if(t[p].add) {
		t[lson].add += t[p].add;
		t[rson].add += t[p].add;
		t[lson].x += t[p].add * (t[lson].r - t[lson].l + 1);
		t[rson].x += t[p].add * (t[rson].r - t[rson].l + 1);
		t[p].add = 0;
	} 
}

void Update(int p, int l, int r, int x) {
	if(l <= t[p].l && t[p].r <= r) {
		t[p].x += x * (t[p].r - t[p].l + 1);
		t[p].add += x;
		return ;
	}
	Spread(p);
	int mid = (t[p].l + t[p].r) >> 1;
	if(l <= mid)
		Update(lson, l, r, x);
	if(r > mid)
		Update(rson, l, r, x);
	Push_Up(p);
}

int Query(int p, int l, int r) {
	if(l <= t[p].l && t[p].r <= r)
		return t[p].x;
	Spread(p);
	int mid = (t[p].l + t[p].r) >> 1, val = 0;
	if(l <= mid)
		val += Query(lson, l, r);
	if(r > mid)
		val += Query(rson, l, r);
	return val;
}

int main() {
	int n = read(), m = read(), last = 0;
	for(int i = 1; i <= n; i++) {
		int x = read();
		num[i] = x - last;
		last = x;
	}
	Make_Tree(1, 1, n + 1);
	for(int i = 1; i <= m; i++) {
		int op = read();
		if(op == 1) {
			int l = read(), r = read(), k = read(), d = read();
			Update(1, l, l, k);
			Update(1, l + 1, r, d);
			Update(1, r + 1, r + 1, - (k + (r - l) * d));
		}
		else if(op == 2) {
			int p = read();
			print(Query(1, 1, p), '
');			
		}
	}
	return 0;
}

T2 方差

来自:link

是不是定标定简单了www,好恶心。

对于平均数,不难想到线段树维护区间和。对于数列 (A) 区间 ([l, r]) 的平均数 (a = frac {sum_{i = l}^r {A_i}} {r - l + 1})

对于方差 (s^2)……我们尝试展开方差的公式。

(s^2 * len = sum_{i = l}^r (A_i - a)^2(len = r - l + 1)),其中 (a)(A_i(i in [l, r])) 的平均数。

(s^2 * len = sum_{i = l}^r ({A_i}^2 - 2 imes A_i imes a + a^2)(len = r - l + 1))

可得 (s^2 * len = sum_{i = l}^r {A_i}^2 - 2 imes a imes sum_{i = l}^r {A_i} + len imes a^2)

(a) 可以由第一问求得,(sum_{i = l}^r {A_i}) 就是线段树维护的东西。于是我们考虑维护 (sum_{i = l}^r {A_i}^2) 即可。

这个也不难嘛。因为是区间修改,所以我们考虑一下如何向下传递懒标。

(sum_{i = l}r (A_i + k) ^ 2 = sum_{i = l}r ({A_i} ^ 2 + 2 imes k imes A_i + k ^ 2))

(sum_{i = l}^r (A_i + k) ^ 2 = sum_{i = l}^r {A_i} ^ 2 + 2 imes k imes (sum_{i - l}^r A_i) + k ^ 2 imes (r - l + 1))

(k) 就是我们的懒标,而 (sum_{i = l}^r {A_i} ^ 2)(sum_{i - l}^r A_i) 由线段树维护。接下来就是实现了。

#include <cstdio>
#define lson p << 1
#define rson p << 1 | 1

int read() {
    int k = 1, x = 0;
    char s = getchar();
    while (s < '0' || s > '9') {
        if (s == '-')
            k = -1;
        s = getchar();
    }
    while (s >= '0' && s <= '9') {
        x = (x << 3) + (x << 1) + s - '0';
        s = getchar();
    }
    return x * k;
}

void write(int x) {
    if(x < 0) {
    	putchar('-');
		x = -x;
    }
    if(x > 9)
		write(x / 10);
    putchar(x % 10 + '0');
}

void print(int x, char s) {
	write(x);
	putchar(s);
}

const int MAXN = 1e5 + 5;
const int MAXT = MAXN * 4 + 5;

struct Segment_Tree {
	int l, r;
	double sum, add, q;
	Segment_Tree() {}
	Segment_Tree(int L, int R, double Sum, double Add, double Q) {
		l = L;
		r = R;
		sum = Sum;
		add = Add;
		q = Q;
	}
} t[MAXT];
double a[MAXN];

void Push_Up(int p) {
	t[p].sum = t[lson].sum + t[rson].sum;
	t[p].q = t[lson].q + t[rson].q;
}

void Make_Tree(int p, int l, int r) {
	t[p].l = l;
	t[p].r = r;
	if(l == r) {
		t[p].add = 0;
		t[p].q = a[l] * a[l];
		t[p].sum = a[l];
		return ;
	}
	int mid = (l + r) >> 1;
	Make_Tree(lson, l, mid);
	Make_Tree(rson, mid + 1, r);
	Push_Up(p);
}

void Spread(int p) {
	if(t[p].add) {
		t[lson].q += ((t[p].add * t[p].add) * (t[lson].r - t[lson].l + 1) + 2 * t[p].add * t[lson].sum);		
		t[rson].q += ((t[p].add * t[p].add) * (t[rson].r - t[rson].l + 1) + 2 * t[p].add * t[rson].sum);		
		t[lson].sum += t[p].add * (t[lson].r - t[lson].l + 1);
		t[rson].sum += t[p].add * (t[rson].r - t[rson].l + 1);
		t[lson].add += t[p].add;
		t[rson].add += t[p].add;
		t[p].add = 0;
	}
}

void Update(int p, int l, int r, double x) {
	if(l <= t[p].l && t[p].r <= r) {
		t[p].add += x;
		t[p].q += ((x * x) * (t[p].r - t[p].l + 1) + 2 * x * t[p].sum);		
		t[p].sum += x * (t[p].r - t[p].l + 1);
		return ;
	}
	Spread(p);
	int mid = (t[p].l + t[p].r) >> 1;
	if(l <= mid)
		Update(lson, l, r, x);
	if(r > mid)
		Update(rson, l, r, x);
	Push_Up(p);
}

double Query(int p, int l, int r, int flag) {
	if(l <= t[p].l && t[p].r <= r) 
		return flag == 1 ? t[p].sum : t[p].q;
	Spread(p);
	int mid = (t[p].l + t[p].r) >> 1;
	double val = 0;
	if(l <= mid) 
		val += Query(lson, l, r, flag);
	if(r > mid)
		val += Query(rson, l, r, flag);
	return val;
}

int main() {
	int n = read(), m = read();
	for(int i = 1; i <= n; i++)
		scanf ("%lf", &a[i]);
	Make_Tree(1, 1, n);
	for(int i = 1; i <= m; i++) {
		int op = read(), l = read(), r = read();
		if(op == 1) {
			double x;
			scanf ("%lf", &x);
			Update(1, l, r, x);
		}
		else if(op == 2)
			printf("%.4lf
", Query(1, l, r, 1) / (r - l + 1));
		else if(op == 3) {
			double sum = Query(1, l, r, 1), cnt = sum / (r - l + 1);
			double ans = Query(1, l, r, 2) + (r - l + 1) * cnt * cnt - 2 * cnt * sum;
			printf("%.4lf
", ans / (r - l + 1));			
		}
	}
	return 0;
}

T3 色板游戏

来自:link

逐渐降智。。。于是胡了一个吸氧才能过的算法。

看到这道题,(t) 还挺小,于是想到建 (t) 个线段树。

对于修改:假设将 (l)(r) 涂上 (k),则在第 (k) 个线段树上操作,将 (l)(r) 打上涂色标记,在其余线段树上抹去 (l)(r) 的涂色标记。

查询就很简单了,我们遍历每棵树,如果这棵树上有节点有涂色标记则累加答案。

哇!这道题卡我空间!

#include <cstdio>
#define lson p << 1
#define rson p << 1 | 1

int read() {
    int k = 1, x = 0;
    char s = getchar();
    while (s < '0' || s > '9') {
        if (s == '-')
            k = -1;
        s = getchar();
    }
    while (s >= '0' && s <= '9') {
        x = (x << 3) + (x << 1) + s - '0';
        s = getchar();
    }
    return x * k;
}

void write(int x) {
    if(x < 0) {
    	putchar('-');
		x = -x;
    }
    if(x > 9)
		write(x / 10);
    putchar(x % 10 + '0');
}

void print(int x, char s) {
	write(x);
	putchar(s);
}

const int MAXN = 1e5 + 5;
const int MAXT = MAXN * 4;

struct Segment_Tree {
	int l, r, lazy;
	bool flag;
	Segment_Tree() {}
	Segment_Tree(int L, int R, bool Flag, int Lazy) {
		l = L;
		r = R;
		flag = Flag;
		lazy = Lazy;
	}
} t[MAXT][31];
// 卡空间。不要开 35,开 31。

void Push_Up(int p, int num) {
	t[p][num].flag = t[lson][num].flag | t[rson][num].flag;
}

void Make_Tree(int p, int l, int r, int num) {
	t[p][num].l = l;
	t[p][num].r = r;
	t[p][num].lazy = -1;
	if(l == r) {
		t[p][num].flag = (num == 1);
		t[p][num].lazy = -1;
		return ;
	}
	int mid = (l + r) >> 1;
	Make_Tree(lson, l, mid, num);
	Make_Tree(rson, mid + 1, r, num);
	Push_Up(p, num);
}

void Spread(int p, int num) {
	if(~t[p][num].lazy) {
		t[lson][num].flag = t[p][num].lazy;
		t[rson][num].flag = t[p][num].lazy;
		t[lson][num].lazy = t[p][num].lazy;
		t[rson][num].lazy = t[p][num].lazy;
		t[p][num].lazy = -1;
	}
}

void Update(int p, int l, int r, bool k, int num) {
	if(l <= t[p][num].l && t[p][num].r <= r) {
		t[p][num].flag = k;
		t[p][num].lazy = k;
		return ;
	}
	Spread(p, num);
	int mid = (t[p][num].l + t[p][num].r) >> 1;
	if(l <= mid)
		Update(lson, l, r, k, num);
	if(r > mid)
		Update(rson, l, r, k, num);
	Push_Up(p, num);
}

bool Query(int p, int l, int r, int num) {
	if(l <= t[p][num].l && t[p][num].r <= r) 
		return t[p][num].flag;
	Spread(p, num);
	int mid = (t[p][num].l + t[p][num].r) >> 1;
	bool val = 0;
	if(l <= mid)
		val |= Query(lson, l, r, num);
	if(r > mid)
		val |= Query(rson, l, r, num);
	return val;
}

bool vis[MAXN];
int a[MAXN], len;

int main() {
	int n = read(), T = read(), m = read();
	Make_Tree(1, 1, n, 1);
	vis[1] = true;
	a[++len] = 1;
	for(int i = 1; i <= m; i++) {
		char op[2];
		scanf ("%s", op);
		int l = read(), r = read();	
		if(l > r) {
			int t = l;
			l = r;
			r = t;
		}
		if(op[0] == 'C') {
			int k = read();	
			if(!vis[k]) {
				vis[k] = true;
				Make_Tree(1, 1, n, k);	
				a[++len] = k;				
			}
			for(int j = 1; j <= len; j++)
				Update(1, l, r, (a[j] == k), a[j]);  
		}
		else if(op[0] == 'P') {
			int ans = 0;
			for(int j = 1; j <= len; j++)
				if(Query(1, l, r, a[j]))
					ans++;
			print(ans, '
');
		}
	}
	return 0;
}

T4 上帝造题的七分钟2

来自:link

说实话,这个优化好像还挺经典的。

这道题仔细思考后,发现不会打懒标,于是转移思路。

想到 (sqrt x) 的性质,首先对于一个极大的数,开方开不了几次这个数向下取整就会 (leq 1)

而我们又知道 (lfloor sqrt x floor = x(x leq 1))

那么所以每次在修改时,我们维护一个区间最大,如果这个最大值 (leq 1) 直接结束这个支路的更新即可。

按理说,这样会节约很多时间,最坏时间复杂度 (O(n imes log(n) + sum_{i = 1}^{6 imes n} n imes (log(n) - i + 1) + (m - 6 imes n) imes log(n)))

#include <cstdio>
#include <cmath>
#define lson p << 1
#define rson p << 1 | 1
#define Max(x, y) x > y ? x : y
#define Swap(x, y) {int t = x; x = y; y = t;}

typedef long long LL;

LL read() {
    int k = 1;
	LL x = 0;
    char s = getchar();
    while (s < '0' || s > '9') {
        if (s == '-')
            k = -1;
        s = getchar();
    }
    while (s >= '0' && s <= '9') {
        x = (x << 3) + (x << 1) + s - '0';
        s = getchar();
    }
    return x * k;
}

void write(LL x) {
    if(x < 0) {
    	putchar('-');
		x = -x;
    }
    if(x > 9)
		write(x / 10);
    putchar(x % 10 + '0');
}

void print(LL x, char s) {
	write(x);
	putchar(s);
}

const int MAXN = 1e5 + 5;
const int MAXT = MAXN * 4;
LL a[MAXN];

struct Segment_Tree {
	int l, r;
	LL sum, ma;
	Segment_Tree() {}
	Segment_Tree(int L, int R, LL Ma, LL Sum) {
		l = L;
		r = R;
		ma = Ma;
		sum = Sum;
	}
} t[MAXT];

void Push_Up(int p) {
	t[p].ma = Max(t[lson].ma, t[rson].ma);
	t[p].sum = t[lson].sum + t[rson].sum;
}

void Make_Tree(int p, int l, int r) {
	t[p].l = l;
	t[p].r = r;
	if(l == r) {
		t[p].ma = a[l];
		t[p].sum = a[l];
		return ;
	}
	int mid = (l + r) >> 1;
	Make_Tree(lson, l, mid);
	Make_Tree(rson, mid + 1, r);
	Push_Up(p);
}

void Update(int p, int l, int r) {
	if(t[p].l == t[p].r) {
		t[p].ma = sqrt(t[p].ma);
		t[p].sum = sqrt(t[p].sum);
		return ;
	}
	if(t[p].ma <= 1) 
		return ;	
	int mid = (t[p].l + t[p].r) >> 1;
	if(l <= mid)
		Update(lson, l, r);
	if(r > mid)
		Update(rson, l, r);
	Push_Up(p);
}

LL Query(int p, int l, int r) {
	if(l <= t[p].l && t[p].r <= r) 
		return t[p].sum;
	int mid = (t[p].l + t[p].r) >> 1;
	LL val = 0;
	if(l <= mid)
		val += Query(lson, l, r);
	if(r > mid)
		val += Query(rson, l, r);
	return val;
}
	
int main() {
	int n = read();
	for(int i = 1; i <= n; i++)
		a[i] = read();
	Make_Tree(1, 1, n);
	int m = read();
	for(int i = 1; i <= m; i++) {
		int k = read(), l = read(), r = read();
		if(l > r)
			Swap(l, r);
		if(k == 0)
			Update(1, l, r);
		else if(k == 1)
			print(Query(1, l, r), '
');
	}
	return 0;
}

T5 World of Darkraft: Battle for Azathoth

来自:link

名字好长a

抽象题意:给出三个数列。

第一个数列 (A)(n) 项,每一项 (A_i) 包含两个元素 (A1_i)(A2_i)

第一个数列 (B)(m) 项,每一项 (B_i) 包含两个元素 (B1_i)(B2_i)

第一个数列 (C)(p) 项,每一项 (C_i) 包含三个元素 (C1_i)(C2_i)(C3_i)

(f(i, j) = sum_{k = 1}^p C3_k(C1_k < A1_i, C2_k < B1_j, i in [1, n], j in [1, m]) - A2_i - B2_j)

求出 (max(f(i, j)(i in [1, n], j in [1, m])))

对于这样一个题目,我们先将三个序列调整至一个优秀的顺序。

(A) 序列按 (A1) 升序排序,(B) 序列按 (B1) 升序排序,(C) 序列以 (C1) 为第一关键字,(C2) 为第二关键字排序。

那么显然有这样一个性质:若 (A1_i > C1_k,B1_j > C2_k),则对于所有 (b in [j, m]),都有 (A1_i > C1_k,B1_b > C2_k)

这个“显然性质”我们先放着。

首先,我们利用双指针的遍历方式。在一开始,定义指针 (i) 指向序列 (A) 的第一项,指针 (j) 指向序列 (C) 的第一项。

1.(C1_j < A1_i),则我们去二分查找 (B) 序列里的一个数使得 (B1_k > C2_j (k in [1, m]))(B1_{k - 1} < C2_j (k in [1, m], B1_0 = 0))。根据性质,此时显然对于所有 (b in [k, m]),都能使得 (A1_i > C1_k,B1_b > C2_k) 成立,即所有的 (b in [k, m]) 都能与当前的 (i) 收获 (C3_j)

这明显是一个区间修改的线段树。我们将区间 ([k, m]) 全部累加上 (C3_j),且区间 ([1, m]) 初始值为 (-B2_b(b in [1, m])),维护区间最大值。

这个最大值记为 (max),则我们当前对于 (A_i) 的答案就是 (max - A2_i),更新全局答案即可。

做完这一步后,移动指针 (j)(j = j + 1)

2.(C1_j geq A1_i),则我们移动指针 (i)(i = i + 1)

值得 注意 的是,若 (i) 先到 (n) 不会影响答案。但如果 (j) 先到 (p) 呢?

我们依然需要走完后面的 (i),因为现在的确不能再更新当前状态解了,但可以更新全局答案。

#include <cstdio>
#include <algorithm>
using namespace std;
#define lson p << 1
#define rson p << 1 | 1

int read() {
    int k = 1, x = 0;
    char s = getchar();
    while (s < '0' || s > '9') {
        if (s == '-')
            k = -1;
        s = getchar();
    }
    while (s >= '0' && s <= '9') {
        x = (x << 3) + (x << 1) + s - '0';
        s = getchar();
    }
    return x * k;
}

void write(int x) {
    if(x < 0) {
    	putchar('-');
		x = -x;
    }
    if(x > 9)
		write(x / 10);
    putchar(x % 10 + '0');
}

void print(int x, char s) {
	write(x);
	putchar(s);
}

int Max(int x, int y) {return x > y ? x : y;}

const int INF = 2147483647;
const int MAXN = 2 * 1e5 + 5;
const int MAXT = 4 * MAXN;

struct Data {
	int v, cost;
	Data() {}
	Data(int V, int Cost) {
		v = V;
		cost = Cost;
	}
} a[MAXN], b[MAXN];

bool cmp1(Data x, Data y) {
	if(x.v == y.v)
		return x.cost < y.cost;
	return x.v < y.v;
}

struct monster {
	int va, vb, cost;
	monster() {}
	monster(int Va, int Vb, int Cost) {
		va = Va;
		vb = Vb;
		cost = Cost;
	}
} c[MAXN];

bool cmp2(monster x, monster y) {
	if(x.va == y.va) {
		if(x.vb == y.vb)
			return x.cost > y.cost;
		else
			return x.vb < y.vb;
	}
	return x.va < y.va;
}

struct Segment_Tree {
	int l, r, ma, add;
	Segment_Tree() {}
	Segment_Tree(int L, int R, int Ma, int Add) {
		l = L;
		r = R;
		Ma = ma;
		add = Add;
	}
} t[MAXT];

void Push_Up(int p) {
	t[p].ma = Max(t[lson].ma, t[rson].ma);
}

void Make_Tree(int p, int l, int r) {
	t[p].l = l;
	t[p].r = r;
	if(l == r) {
		t[p].ma = (-b[l].cost);
		t[p].add = 0;
		return ;
	}
	int mid = (l + r) >> 1;
	Make_Tree(lson, l, mid);
	Make_Tree(rson, mid + 1, r);
	Push_Up(p);
}

void Spread(int p) {
	if(t[p].add) {
		t[lson].ma += t[p].add;
		t[rson].ma += t[p].add;
		t[lson].add += t[p].add;
		t[rson].add += t[p].add;
		t[p].add = 0;
	}
}

void Update(int p, int l, int r, int x) {
	if(l <= t[p].l && t[p].r <= r) {
		t[p].add += x;
		t[p].ma += x;
		return ;
	}
	Spread(p);
	int mid = (t[p].l + t[p].r) >> 1;
	if(l <= mid)
		Update(lson, l, r, x);
	if(r > mid)
		Update(rson, l, r, x);
	Push_Up(p);
}

int main() {
	int n = read(), m = read(), p = read();
	for(int i = 1; i <= n; i++)
		a[i].v = read(), a[i].cost = read();
	sort(a + 1, a + n + 1, cmp1);

	for(int i = 1; i <= m; i++)
		b[i].v = read(), b[i].cost = read();
	sort(b + 1, b + m + 1, cmp1);

	for(int i = 1; i <= p; i++)
		c[i].va = read(), c[i].vb = read(), c[i].cost = read();
	sort(c + 1, c + p + 1, cmp2);
	int ans = -INF, j = 1;
	Make_Tree(1, 1, m);
	for(int i = 1; i <= n; i++) {
		while(j <= p && c[j].va < a[i].v) {
			int l = 1, r = m, res = 0;
			while(l <= r) {
				int mid = (l + r) >> 1;
				if(b[mid].v > c[j].vb) {
					r = mid - 1;
					res = mid;
				}
				else
					l = mid + 1;
			}
			if(res)
				Update(1, res, m, c[j].cost);
			j++;
		}
		ans = Max(t[1].ma - a[i].cost, ans);
	}
	print(ans, '
');
	return 0;
}

T6 康娜的线段树

link

这个方面的知识点好像挺新颖的。

于是和 JC 一起想出了该命题的 (O(n)) 解法。

命题指:序列元素在线段树上的深度

总所周知,线段树上的节点都对应表示的原序列里的一些结点。

而我们现在需要解决的问题就是:在极快的时间复杂度内求到每个原序列里的元素对应的元区间在线段树中的深度。

也就是求每个叶子节点的深度。

用线段树建树的朴素做法显然是:(O(nlogn))

但有些题目会比较恶心,于是我们考虑一种新的做法。

首先明确线段树的一个性质,如果树上有两个节点,且这两个结点表示区间长度相同,则处于相对位置相同的两个分别在这两个节点表示的区间中的原序列中的元素表示的元区间分别到这两个节点的距离相等。(好抽象 www。

于是我们将其剥离出来。

即有两个结点 (p,q),其中 (p) 表示区间 (A)(q) 表示区间 (B),且区间 (A) 的右端点为 (A_l),区间 (B) 的右端点为 (B_l),且记 (f(x)) 表示 (x) 为当前所在区间的第几个元素。

则对于任意两点 (m,n)(m in A,n in B, f(m) = f(n)),一定有 (p) 到表示 (m) 的元区间的叶子节点的距离等于一定有 (q) 到表示 (n) 的元区间的叶子节点的距离。

这其实很显然吧。。因为对于每个表示区间长度的节点,我们线段树往下划分的方式是不变的。

接下来,我们记 (dep(x)) 表示 (x) 这个元区间到根节点的距离,即表示 (x) 这个元区间的叶子节点的深度。(Dep(x)) 表示 (x) 这个节点的深度。

那么如果我们现在遍历到了一个节点 (Q),它表示的区间长度为 (len),而我们之前也遍历过一个表示区间长度为 (len) 的节点 (P),则定会有 (dep(x) = dep(y) - Dep(P) + Dep(Q) (x in Q,y in P, f(x) = f(y)))

这是因为我们有刚刚那个性质嘛,(x) 这个元区间对应的叶子节点的深度可以分解为这个节点到 (Q) 的距离和 (Q) 的深度。因为 (y) 的深度也可以同样分解,所以前者就等于 (dep(y) - Dep(p))

那么我们可以利用一个 dfs,遍历线段树上的节点,如果遇到一个节点且之前遇到过表示区间长度相同的节点,则我们可以直接用之前那个点对当前节点表示区间内的所有元素进行深度转移,然后这个分支就可以结束了。

因为有记忆化,且你会发现每个节点我们只会更新一次,于是这就是个类 (O(n)) 算法。

这道题就可以用我们的思路进行预处理。

首先此题是求在线段树中从根到某一叶子节点经过路径权值和的期望。

朴素期望公式:一颗维护区间和的线段树,答案为每个节点表示的权值乘上每个节点的深度,然后在将它们全部加起来。

于是我们将每个节点的权值再返回到原序列中。

设原序列中元素 (x) 表示的元区间的深度为 (g(x)),其表示的数为 (v(x))

则原序列的每个元素会对答案产生的贡献为:(v(x) imes sum_{i = 1}^{g(x)} 2^i)

很显然当前这个元素在线段树种,会在其元区间到根的每一个节点表示的区间里出现。

其中 (sum_{i = 1}^{g(x)} 2^i) 显然可以用上述算法预处理出来。

那么考虑区修。设所改区间为 ([l, r])。增加量为 (x)

则这次修改对答案产生的贡献就是 (Δ imes sum_{x = l}^rsum_{i = 1}^{g(x)} 2^i)

那么再维护一个 (sum_{i = 1}^{g(x)} 2^i) 的前缀和不就结了吗?

(注,此题若用 double 会错掉一个点,可能与数据精度有关,建议直接使用 long long

#include <cstdio>

typedef long long LL;
int read() {
    int k = 1, x = 0;
    char s = getchar();
    while (s < '0' || s > '9') {
        if (s == '-')
            k = -1;
        s = getchar();
    }
    while (s >= '0' && s <= '9') {
        x = (x << 3) + (x << 1) + s - '0';
        s = getchar();
    }
    return x * k;
}

const int MAXN = 1e6 + 5;
int a[MAXN], dep[MAXN];
LL w[MAXN], sum[MAXN];

struct node {
	bool flag;
	int l, x;
	node() {}
	node(bool Flag, int L, int X) {
		flag = Flag;
		l = L;
		x = X;
	}
} q[MAXN];

void Get_Dep(int l, int r, int cnt) {
	if(q[r - l + 1].flag) {
		for(int i = l; i <= r; i++)
			dep[i] = dep[i - l + q[r - l + 1].l] - q[r - l + 1].x + cnt;
		return ;
	}
	if(l == r) {
		dep[l] = cnt;
		return ;
	}
	int mid = (l + r) >> 1;
	Get_Dep(l, mid, cnt + 1);
	Get_Dep(mid + 1, r, cnt + 1);
	q[r - l + 1].flag = true;
	q[r - l + 1].l = l;
	q[r - l + 1].x = cnt;
}

int main() {
	int n = read(), m = read(), qwq = read();
	Get_Dep(1, n, 1);	
	w[1] = qwq;
	for(int i = 2; i <= 23; i++) 
		w[i] = w[i - 1] + (qwq >> (i - 1));
	for(int i = 1; i <= n; i++) 
		sum[i] = sum[i - 1] + w[dep[i]];	
	LL ans = 0;	
	for(int i = 1; i <= n; i++) {
		a[i] = read();
		ans += (a[i] * (sum[i] - sum[i - 1]));		
	}	
	for(int i = 1; i <= m; i++) {
		int l = read(), r = read(), x = read();
		ans += ((sum[r] - sum[l - 1]) * x);
		printf("%lld
", ans);
	}
	return 0;
}
原文地址:https://www.cnblogs.com/Chain-Forward-Star/p/14171339.html