Solution -「线段树」题目集合
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 \times (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) \times 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), '\n');
}
}
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 \times A_i \times a + a^2)(len = r - l + 1)\)
可得 \(s^2 * len = \sum_{i = l}^r {A_i}^2 - 2 \times a \times \sum_{i = l}^r {A_i} + len \times 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 \times k \times A_i + k ^ 2)\)。
即 \(\sum_{i = l}^r (A_i + k) ^ 2 = \sum_{i = l}^r {A_i} ^ 2 + 2 \times k \times (\sum_{i - l}^r A_i) + k ^ 2 \times (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\n", 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\n", 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, '\n');
}
}
return 0;
}
T4 上帝造题的七分钟2
来自:Link
说实话,这个优化好像还挺经典的。
这道题仔细思考后,发现不会打懒标,于是转移思路。
想到 \(\sqrt x\) 的性质,首先对于一个极大的数,开方开不了几次这个数向下取整就会 \(\leq 1\)。
而我们又知道 \(\lfloor \sqrt x \rfloor = x(x \leq 1)\)。
那么所以每次在修改时,我们维护一个区间最大,如果这个最大值 \(\leq 1\) 直接结束这个支路的更新即可。
按理说,这样会节约很多时间,最坏时间复杂度 \(O(n \times log(n) + \sum_{i = 1}^{6 \times n} n \times (log(n) - i + 1) + (m - 6 \times n) \times 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), '\n');
}
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, '\n');
return 0;
}
T6 康娜的线段树
这个方面的知识点好像挺新颖的。
于是和 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) \times \sum_{i = 1}^{g(x)} 2^i\)。
很显然当前这个元素在线段树种,会在其元区间到根的每一个节点表示的区间里出现。
其中 \(\sum_{i = 1}^{g(x)} 2^i\) 显然可以用上述算法预处理出来。
那么考虑区修。设所改区间为 \([l, r]\)。增加量为 \(x\)。
则这次修改对答案产生的贡献就是 \(Δ \times \sum_{x = l}^r\sum_{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\n", ans);
}
return 0;
}
T7 序列操作
来自:Link
啊,这道题调了我三天。
思想上是不难的,主要是维护的东西有点多。
对于两个答案我们维护:
- 当前区间 \(1\) 的个数。
- 当前区间最多连续 \(1\) 的个数。
对于第二个值我们还需要维护:
- 左前缀最长连续 \(1\) 的个数。
- 右前缀最长连续 \(1\) 的个数。
- 这样记录的话,当前区间最多连续 \(1\) 的个数,就等于左儿子的左前缀最长连续 \(1\) 的个数,右儿子的右前缀最长连续 \(1\) 的个数,右儿子的左前缀最长连续 \(1\) 的个数加上左儿子的右前缀最长连续 \(1\) 的个数这三个量的最大值。
这样每个节点的每个信息都可以由子节点转移了。
又由于是区修,所以需要懒标记。不难发现两大类操作(更改,取反)具有优先级差异。故需要两个标记,且每次打更改标记或下传更改标记时,必须覆盖取反标记。
具体实现看代码吧。。
#include <cstdio>
#define lson p << 1
#define rson p << 1 | 1
int Max(int x, int y) { return x > y ? x : y; }
void Swap(int &x, int &y) {
int t = x;
x = y;
y = t;
}
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;
int a[MAXN];
struct data {
int l, r, x;
data() {}
data(int L, int R, int X) {
l = L;
r = R;
x = X;
}
};
void Swap_(data &X, data &Y) {
data t = X;
X = Y;
Y = t;
}
struct Segment_Tree {
int l, r, sumo, sumz, lazy;
bool exlazy;
data resz, reso;
Segment_Tree() {}
Segment_Tree(int L, int R, data Resz, data Reso, int Sumo, int Sumz, int Lazy, bool Exlazy) {
l = L;
r = R;
resz = Resz;
reso = Reso;
sumo = Sumo;
sumz = Sumz;
lazy = Lazy;
exlazy = Exlazy;
}
} t[MAXN * 4];
void Push_Up(int p) {
if (t[lson].reso.l == t[lson].r - t[lson].l + 1)
t[p].reso.l = t[lson].r - t[lson].l + 1 + t[rson].reso.l;
else
t[p].reso.l = t[lson].reso.l;
if (t[rson].reso.r == t[rson].r - t[rson].l + 1)
t[p].reso.r = t[rson].r - t[rson].l + 1 + t[lson].reso.r;
else
t[p].reso.r = t[rson].reso.r;
if (t[lson].resz.l == t[lson].r - t[lson].l + 1)
t[p].resz.l = t[lson].r - t[lson].l + 1 + t[rson].resz.l;
else
t[p].resz.l = t[lson].resz.l;
if (t[rson].resz.r == t[rson].r - t[rson].l + 1)
t[p].resz.r = t[rson].r - t[rson].l + 1 + t[lson].resz.r;
else
t[p].resz.r = t[rson].resz.r;
t[p].sumo = t[lson].sumo + t[rson].sumo;
t[p].sumz = t[lson].sumz + t[rson].sumz;
t[p].reso.x = Max(t[lson].reso.x, Max(t[rson].reso.x, t[lson].reso.r + t[rson].reso.l));
t[p].resz.x = Max(t[lson].resz.x, Max(t[rson].resz.x, t[lson].resz.r + t[rson].resz.l));
}
void Spread(int p) {
if (t[p].lazy != -1) {
if (t[p].lazy == 0) {
t[lson].resz =
data(t[lson].r - t[lson].l + 1, t[lson].r - t[lson].l + 1, t[lson].r - t[lson].l + 1);
t[rson].resz =
data(t[rson].r - t[rson].l + 1, t[rson].r - t[rson].l + 1, t[rson].r - t[rson].l + 1);
t[lson].reso = data(0, 0, 0);
t[rson].reso = data(0, 0, 0);
t[lson].sumz = t[lson].r - t[lson].l + 1;
t[rson].sumz = t[rson].r - t[rson].l + 1;
t[lson].sumo = 0;
t[rson].sumo = 0;
t[lson].lazy = 0;
t[rson].lazy = 0;
t[lson].exlazy = false;
t[rson].exlazy = false;
} else if (t[p].lazy == 1) {
t[lson].reso =
data(t[lson].r - t[lson].l + 1, t[lson].r - t[lson].l + 1, t[lson].r - t[lson].l + 1);
t[rson].reso =
data(t[rson].r - t[rson].l + 1, t[rson].r - t[rson].l + 1, t[rson].r - t[rson].l + 1);
t[lson].resz = data(0, 0, 0);
t[rson].resz = data(0, 0, 0);
t[lson].sumo = t[lson].r - t[lson].l + 1;
t[rson].sumo = t[rson].r - t[rson].l + 1;
t[lson].sumz = 0;
t[rson].sumz = 0;
t[lson].lazy = 1;
t[rson].lazy = 1;
t[lson].exlazy = false;
t[rson].exlazy = false;
}
t[p].lazy = -1;
}
if (t[p].exlazy) {
Swap(t[lson].sumo, t[lson].sumz);
Swap_(t[lson].reso, t[lson].resz);
Swap(t[rson].sumo, t[rson].sumz);
Swap_(t[rson].reso, t[rson].resz);
if(t[lson].lazy != -1)
t[lson].lazy ^= 1, t[lson].exlazy = false;
else
t[lson].exlazy ^= 1;
if(t[rson].lazy != -1)
t[rson].lazy ^= 1, t[rson].exlazy = false;
else
t[rson].exlazy ^= 1;
t[p].exlazy = false;
}
}
void Make_Tree(int p, int l, int r) {
t[p].l = l;
t[p].r = r;
t[p].lazy = -1;
t[p].exlazy = false;
if (l == r) {
if (a[l] == 1) {
t[p].reso = data(1, 1, 1);
t[p].sumo = 1;
t[p].resz = data(0, 0, 0);
t[p].sumz = 0;
} else {
t[p].resz = data(1, 1, 1);
t[p].sumz = 1;
t[p].reso = data(0, 0, 0);
t[p].sumo = 0;
}
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, int flag) {
if (l <= t[p].l && t[p].r <= r) {
if (flag == 0) {
t[p].exlazy = false;
t[p].lazy = 0;
t[p].sumz = t[p].r - t[p].l + 1;
t[p].sumo = 0;
t[p].resz = data(t[p].r - t[p].l + 1, t[p].r - t[p].l + 1, t[p].r - t[p].l + 1);
t[p].reso = data(0, 0, 0);
} else if (flag == 1) {
t[p].exlazy = false;
t[p].lazy = 1;
t[p].sumo = t[p].r - t[p].l + 1;
t[p].sumz = 0;
t[p].reso = data(t[p].r - t[p].l + 1, t[p].r - t[p].l + 1, t[p].r - t[p].l + 1);
t[p].resz = data(0, 0, 0);
} else if (flag == 2) {
t[p].exlazy ^= 1;
Swap(t[p].sumo, t[p].sumz);
Swap_(t[p].reso, t[p].resz);
}
Spread(p);
return;
}
Spread(p);
int mid = (t[p].l + t[p].r) >> 1;
if (l <= mid)
Update(lson, l, r, flag);
if (r > mid)
Update(rson, l, r, flag);
Push_Up(p);
}
Segment_Tree Query(int p, int l, int r) {
if (l <= t[p].l && t[p].r <= r)
return t[p];
Spread(p);
int mid = (t[p].l + t[p].r) >> 1;
if (r <= mid)
return Query(lson, l, r);
if (l > mid)
return Query(rson, l, r);
Segment_Tree ret, Lson = Query(lson, l, r), Rson = Query(rson, l, r);
ret.l = Lson.l, ret.r = Rson.r;
ret.sumo = Lson.sumo + Rson.sumo;
ret.sumz = Lson.sumz + Rson.sumz;
if (Lson.reso.l == Lson.r - Lson.l + 1)
ret.reso.l = Lson.r - Lson.l + 1 + Rson.reso.l;
else
ret.reso.l = Lson.reso.l;
if (Rson.reso.r == Rson.r - Rson.l + 1)
ret.reso.r = Rson.r - Rson.l + 1 + Lson.reso.r;
else
ret.reso.r = Rson.reso.r;
if (Lson.resz.l == Lson.r - Lson.l + 1)
ret.resz.l = Lson.r - Lson.l + 1 + Rson.resz.l;
else
ret.resz.l = Lson.resz.l;
if (Rson.resz.r == Rson.r - Rson.l + 1)
ret.resz.r = Rson.r - Rson.l + 1 + Lson.resz.r;
else
ret.resz.r = Rson.resz.r;
ret.reso.x = Max(Lson.reso.x, Max(Rson.reso.x, Lson.reso.r + Rson.reso.l));
ret.resz.x = Max(Lson.resz.x, Max(Rson.resz.x, Lson.resz.r + Rson.resz.l));
return ret;
}
int main() {
int n = read(), m = read();
for (int i = 1; i <= n; i++) a[i] = read();
Make_Tree(1, 1, n);
for (int i = 1; i <= m; i++) {
int opt = read(), l = read(), r = read();
l++;
r++;
if (opt == 0)
Update(1, l, r, 0);
else if (opt == 1)
Update(1, l, r, 1);
else if (opt == 2)
Update(1, l, r, 2);
else if (opt == 3)
print(Query(1, l, r).sumo, '\n');
else
print(Query(1, l, r).reso.x, '\n');
}
return 0;
}
Solution -「线段树」题目集合的更多相关文章
- 「线段树」「单点修改」洛谷P1198 [JSOI2008]最大数
「线段树」「单点修改」洛谷P1198 [JSOI2008]最大数 题面描述 现在请求你维护一个数列,要求提供以下两种操作: 1. 查询操作. 语法:Q L 功能:查询当前数列中末尾L个数中的最大的数, ...
- 「CF319E」Ping-Pong「线段树」「并查集」
题意 规定区间\((a,b)\)到区间\((c,d)\)有边当且仅当\(c<a<d\)或\(c<b<d\). 起初区间集合为空.有\(n\)(\(n\leq 10^5\))次操 ...
- csp-s模拟测试55(9.29)联「线段树」·赛「??」题「神仙DP」
T1 联 考试两个小时终于调过了,话说一个傻逼错最后还是静态查出错的..... 大概维护两个懒标记,一个区间覆盖,一个区间异或,然后保证每个区间只会存在一种懒标记. 然后维护区间0的个数,查询时查询那 ...
- 「CF484E」Sign on Fence「整体二分」「线段树」
题意 给定一个长度为\(n\)的正整数序列,第\(i\)个数为\(h_i\),\(m\)个询问,每次询问\((l, r, w)\),为\([l, r]\)所有长度为\(w\)的子区间最小值的最大值.( ...
- 「CF712E」Memory and Casinos「线段树」「概率」
题解 解法1:(官方做法) 一段区间的\(L\)定义为从最左边开始出发,最左不失败,一直到最右边胜利的概率,\(R\)定义为从最右边开始出发,最左不失败,又回到最右边胜利的概率 考虑一个区间\([l, ...
- Solution -「基环树」做题记录
写的大多只是思路,比较简单的细节和证明过程就不放了,有需者自取. 基环树简介 简单说一说基环树吧.由名字扩展可得这是一类以环为基础的树(当然显然它不是树. 通常的表现形式是一棵树再加一条非树边,把图画 ...
- Solution -「简单 DP」zxy 讲课记实
魔法题位面级乱杀. 「JOISC 2020 Day4」治疗计划 因为是不太聪明的 Joker,我就从头开始理思路了.中途也会说一些和 DP 算法本身有关的杂谈,给自己的冗长题解找借口. 首先,治疗方案 ...
- hdu4973 线段树(题目不错,用了点,段,更新查找还有DFS)
题意: 给你一个初始序列,初始序列长度n,分别为1 2 3 4 5 ....n,有两种操作 (1)D l r 把l_r之间的数据都复制一遍 1 2 3 4 5 6 D 2 4 = 1 2 ...
- Solution -「WC 2022」秃子酋长
\(\mathscr{Description}\) Link. (It's empty temporarily.) 给定排列 \(\{a_n\}\),\(q\) 次询问,每次给出 \([l,r ...
随机推荐
- CentOS 并没有死,Rocky Linux 让其重生
点击上方"开源Linux",选择"设为星标" 回复"学习"获取独家整理的学习资料! 近日,CentOS 官方发文称CentOS Stream ...
- IP地址的格式和分类,你都清楚吗?
一个执着于技术的公众号 在网际层中,利用 IP 地址将数据传输到目的地.为了能够使数据正确地发送到目标主机上,网络上的 IP 地址必须有一定的规则来识别主机的位置. IP地址的基本构成 为了便于寻址, ...
- 设计模式---单例模式,pickle模块
设计模式---单例模式 简介 单例模式(Singleton Pattern) 是一种常用的软件设计模式,该模式的主要目的是确保某一个类只有一个实 例存在.当你希望在整个系统中,某个类只能出现一个实例时 ...
- zabbix5.0报错PHP时区未设置(配置参数"date.timezone")
解决办法 : #1.编辑文件/etc/opt/rh/rh-php72/php-fpm.d/zabbix.conf,取消注释并设置为所在地时区 vim /etc/opt/rh/rh-php72/php- ...
- SUSE系统---keepalived的搭建
作为博客第一篇,先小尝初试一下搭建keepalived,因为项目需要搭建集群,为了满足需要,需要服务器对外暴露个虚拟IP,进行集群部署. 第一步:先把keepalived.tra.gz压缩包放到相应目 ...
- 基于SqlSugar的开发框架循序渐进介绍(4)-- 在数据访问基类中对GUID主键进行自动赋值处理
我们在设计数据库表的时候,往往为了方便,主键ID一般采用字符串类型或者GUID类型,这样对于数据库表记录的迁移非常方便,而且有时候可以在处理关联记录的时候,提前对应的ID值.但有时候进行数据记录插入的 ...
- 个人NuGet服务搭建,BaGet保姆及部署教程
前言 应该或许大概每个公司都会有自己的NuGet包仓库吧. 不会吧!不会吧!不会吧!不会还没有自己的仓NuGet仓库吧! 开个玩笑,虽然我觉得有没有无所谓,但是为了这篇博客它必须有所谓. 在工具的选择 ...
- mysql Bad handshake
由于 Java 程序访问 MySQL 时,MySQL 抛出 Bad handshake 错误,导致接口抛错,然后在 MySQL 配置文件新增 skip_ssl 配置(忽略 SSL 密钥和证书文件),重 ...
- MySQL分库分表-理论
分库分表的几种方式 把一个实例中的多个数据库拆分到不同的实例 把一个库中的表分离到不同的数据库中 数据库分片前的准备 在数据库并发和负载没有达到限制时,不推荐水平拆分 对一个库中的相关表进行水平拆分到 ...
- 【系统】查看windows系统是否永久激活
查看windows系统是否永久激活 查看激活时间 slmgr.vbs -xpr 查看激活详情 slmgr.vbs -dlv