线段树
\(\text{hdu-1166}\)
给定一个长度为 \(n\) 的序列 \(a_i\),有以下三种操作:
Add x k表示将 \(a_x \gets a_x + k\)。Sub x k表示将 \(a_x \gets a_x - k\)。Query l r表示询问 \(a_l + a_{l+1} + \dots + a_r\)。
\(1 \le n \le 5 \times 10^4\),操作次数 \(\le 4 \times 10^4\)。
最基本的线段树板子。甚至可以用树状数组写,但是这里记录线段树的写法。
首先建树,没什么好写的,就递归下去就好了:
void build(long long p, long long l, long long r) {
t[p].l = l, t[p].r = r;
if(l == r) { t[p].res = a[l]; return; }
long long mid = (l + r) >> 1;
build(p << 1, l, mid), build(p << 1 | 1, mid + 1, r);
t[p].res = t[p << 1].res + t[p << 1 | 1].res;
return;
}
然后查询操作:
long long query(long long p, long long l, long long r) {
if(t[p].l >= l && t[p].r <= r) return t[p].res;
long long mid = (t[p].l + t[p].r) >> 1, res = 0;
if(l <= mid) res += query(p << 1, l, r);
if(r > mid) res += query(p << 1 | 1, l, r);
return res;
}
更新操作:
void update(long long p, long long x, long long k) {
if(t[p].l == t[p].r) { t[p].res += k; return; }
long long mid = (t[p].l + t[p].r) >> 1;
if(x <= mid) update(p << 1, x, k);
else update(p << 1 | 1, x, k);
t[p].res = t[p << 1].res + t[p << 1 | 1].res;
return;
}
注意:查询操作里右子树的判断条件是 r > mid,显然不能取等。
\(\text{uestc-1918}\)
给定一个长度为 \(n\) 的序列 \(a_i\),初始值都为 \(0\),有 \(m\) 次操作:
0 x y表示将 \(a_x \gets y\)。1 l r表示询问 \(a_l,a_{l+1},\dots,a_r\) 去除一个最大值和一个最小值之后的和。
\(1 \le n,m \le 10^6\),\(1 \le l < r \le n\)。
显然维护区间和、\(\max\)、\(\min\) 即可。
\(\text{uestc-1933}\)
给定一个长度为 \(n\) 的序列 \(a_i\),初始值都为 \(0\),有 \(m\) 次操作:
0 l r w表示将 \([l, r]\) 区间内的数 \(+w\)。1 l r 0表示询问 \([l, r]\) 的区间和。
\(1 \le n,m \le 10^6\),\(1 \le l \le r \le n\),\(|w| \le 10^3\)。
区间修改就需要懒标记了,对于每个节点维护一个 \(lz\) 即可,需要写 \(\text{pushdown}\) 函数下传:
void pushdown(long long p) {
long long w = t[p].lz;
t[p << 1].lz += w, t[p << 1 | 1].lz += w;
t[p << 1].res += w * (t[p << 1].r - t[p << 1].l + 1);
t[p << 1 | 1].res += w * (t[p << 1 | 1].r - t[p << 1 | 1].l + 1);
t[p].lz = 0;
return;
}
修改操作也需要稍微变一下:
void update(long long p, long long l, long long r, long long w) {
if(t[p].l >= l && t[p].r <= r) {
t[p].res += w * (t[p].r - t[p].l + 1);
t[p].lz += w;
return;
}
long long mid = (t[p].l + t[p].r) >> 1;
if(t[p].lz) pushdown(p);
if(l <= mid) update(p << 1, l, r, w);
if(r > mid) update(p << 1 | 1, l, r, w);
t[p].res = t[p << 1].res + t[p << 1 | 1].res;
return;
}
注意:下传懒标记为累加操作 t[p << 1].lz += w,而非赋值。且记得调用 \(\text{pushdown}\)。
\(\text{OpenJ_Bailian-3439}\)
跟上一题一摸一样,不写了。
\(\text{zzfls-P122}\)
给定一个长度为 \(n\) 的序列 \(a_i\),每个元素初始值为 \(1\)。同时有 \(n\) 个灯泡对应序列的每个位置,初始状态下都是亮着的,有以下三种操作:
1 p表示按下位置 \(p\) 对应的灯泡开关。2 l r x表示将区间 \([l, r]\) 内所有亮着的灯泡对应位置上的值 \(\times x\)。3 l r x判断区间 \([l, r]\) 内的数是否都是 \(x\) 的倍数。若是,将区间内所有亮着的灯泡对应位置上的值 \(\div x\);否则什么都不做。
对于每个操作 \(3\) 回答是否成功进行了操作,YES/NO。
\(1 \le n, m \le 10^5\),\(1 \le x \le 30\),\(1 \le p \le n\),\(1 \le l \le r \le n\)。
这道题实际上就是线段树维护了区间 \(\min\),然后只有区间加减操作。很适合练手。
模拟赛 \(\text{B}\) 题,大概是个蓝。不过赛时没写正解,就写了个暴力。这里写一下正解的思路。
考虑没有 \(1\) 操作的部分分。注意到 \(x\) 很小,所以 \(a_i\) 的质因数种类不多,只有 \(10\) 种。
对于每个质因数开线段树,维护每个位置当前的质因数次数,区间 \(\min\),则等价于区间加、区间减。
如果加上 \(1\) 操作,我们线段树上分开维护每个区间内”亮着的灯“和”暗着的灯“对应位置的次数 \(\min\)。那么对于 \(1\) 操作等价于对一个叶子节点交换了这两个值,不难维护。
时间复杂度为 \(\Theta (\pi (x)n \log n)\)。
补了一下题,居然一遍过样例,一遍 \(\text{AC}\)。贴一下代码吧:
#include<iostream>
#include<cstdio>
using namespace std;
#define MAXN 100005
#define INF 0x3f3f3f3f
long long read() {
long long x = 0, f = 1;
char c = getchar();
while(c > 57 || c < 48) { if(c == 45) f = -1; c = getchar(); }
while(c >= 48 && c <= 57) { x = (x << 1) + (x << 3) + (c - 48); c = getchar(); }
return x * f;
}
struct node { long long l, r, m0, m1, lz; } t[11][MAXN << 2];
long long n, m, op, l, r, x, p[11];
void pushup(long long x, long long p) {
t[x][p].m1 = min(t[x][p << 1].m1, t[x][p << 1 | 1].m1);
t[x][p].m0 = min(t[x][p << 1].m0, t[x][p << 1 | 1].m0);
return;
}
void build(long long x, long long p, long long l, long long r) {
t[x][p].l = l, t[x][p].r = r;
if(l == r) { t[x][p].m0 = INF; return; }
long long mid = (l + r) >> 1;
build(x, p << 1, l, mid);
build(x, p << 1 | 1, mid + 1, r);
pushup(x, p); return;
}
void pushdown(long long x, long long p) {
long long w = t[x][p].lz;
t[x][p << 1].lz += w, t[x][p << 1 | 1].lz += w;
t[x][p << 1].m1 += w, t[x][p << 1 | 1].m1 += w;
t[x][p].lz = 0;
return;
}
void upd(long long x, long long p, long long k) {
if(t[x][p].l == t[x][p].r) { swap(t[x][p].m0, t[x][p].m1); return; }
long long mid = (t[x][p].l + t[x][p].r) >> 1; pushdown(x, p);
if(k <= mid) upd(x, p << 1, k);
else upd(x, p << 1 | 1, k);
pushup(x, p); return;
}
void update(long long x, long long p, long long l, long long r, long long k) {
if(t[x][p].l >= l && t[x][p].r <= r) {
t[x][p].m1 += k, t[x][p].lz += k;
return;
}
long long mid = (t[x][p].l + t[x][p].r) >> 1; pushdown(x, p);
if(l <= mid) update(x, p << 1, l, r, k);
if(r > mid) update(x, p << 1 | 1, l, r, k);
pushup(x, p); return;
}
long long query(long long x, long long p, long long l, long long r) {
if(t[x][p].l >= l && t[x][p].r <= r) return min(t[x][p].m0, t[x][p].m1);
long long mid = (t[x][p].l + t[x][p].r) >> 1, res = INF; pushdown(x, p);
if(l <= mid) res = min(res, query(x, p << 1, l, r));
if(r > mid) res = min(res, query(x, p << 1 | 1, l, r));
return res;
}
int main() {
freopen("tempIate.in", "r", stdin);
freopen("tempIate.out", "w", stdout);
n = read(), m = read();
p[1] = 2, p[2] = 3, p[3] = 5, p[4] = 7, p[5] = 11;
p[6] = 13, p[7] = 17, p[8] = 19, p[9] = 23, p[10] = 29;
for(int i = 1; i <= 10; i ++) build(i, 1, 1, n);
while(m --) {
op = read();
if(op == 1) {
x = read();
for(int i = 1; i <= 10; i ++) upd(i, 1, x);
}
else if(op == 2) {
l = read(), r = read(), x = read();
for(int i = 1; i <= 10; i ++) {
long long cnt = 0;
while(!(x % p[i])) x /= p[i], cnt ++;
if(!cnt) continue;
update(i, 1, l, r, cnt);
}
}
else {
l = read(), r = read(), x = read();
bool fg = true; long long y = x;
for(int i = 1; i <= 10; i ++) {
long long cnt = 0;
while(!(y % p[i])) y /= p[i], cnt ++;
if(!cnt) continue;
if(query(i, 1, l, r) < cnt) { fg = false; break; }
}
if(!fg) { cout << "NO\n"; continue; }
cout << "YES\n";
for(int i = 1; i <= 10; i ++) {
long long cnt = 0;
while(!(x % p[i])) x /= p[i], cnt ++;
if(!cnt) continue;
update(i, 1, l, r, -cnt);
}
}
}
return 0;
}
\(\text{loj-10129}\)
给定一个长度为 \(n\) 的序列 \(a_i\),初始值都为 \(0\),有 \(m\) 次操作:
1 l r c表示将 \([l, r]\) 区间内的数 \(\times c\)。2 l r c表示将 \([l, r]\) 区间内的数 \(+ c\)。3 l r 0表示询问 \([l, r]\) 的区间和,答案对 \(P\) 取模。
\(1 \le n,q \le 10^5\),\(1 \le l \le r \le n\),\(0 \le c, a_i \le 10^9\),\(1 \le P \le 10^9 + 7\)。
需要维护乘的懒标记和加的懒标记,因此就会出现运算优先级的问题。
于是遵循先乘再加的原则即可,写一个函数处理节点懒标记更新情况:
void upd(long long p, long long mul, long long add) {
t[p].res = (t[p].res * mul + add * (t[p].r - t[p].l + 1)) % P;
t[p].mul = t[p].mul * mul % P;
t[p].add = (t[p].add * mul + add) % P;
return;
}
注意加的懒标记也要乘上 \(mul\)。然后 \(\text{pushdown}\) 也需要改一下:
void pushdown(long long p) {
upd(p << 1, t[p].mul, t[p].add);
upd(p << 1 | 1, t[p].mul, t[p].add);
t[p].add = 0, t[p].mul = 1;
return;
}
剩下的几乎一样。
注意:\(\text{build}\) 时记得初始化乘的懒标记为 \(1\)。取模也要注意。
标记永久化
维护区间修改的方式有两种,一种是懒标记和标记下传,另一种叫做”标记永久化“。
标记永久化,就是不下传标记,在每次查询时把经过的标记累加起来,查询时加起来。
于是只有 \(\text{build}\) 的时候需要 \(\text{pushup}\)。任何时刻都不需要 \(\text{pushdown}\)。
更新操作变成这样:
void update(long long p, long long l, long long r, long long k) {
t[p].res += k * (min(t[p].r, r) - max(t[p].l, l) + 1);
// 至于为什么是交集,因为我们没有 pushup,这个东西相当于 pushup。
if(t[p].l >= l && t[p].r <= r) { t[p].lz += k; return; }
long long mid = (t[p].l + t[p].r) >> 1;
if(l <= mid) update(p << 1, l, r, k);
if(r > mid) update(p << 1 | 1, l, r, k);
return;
}
于是查询的时候就要多记录一个懒标记的值:
long long query(long long p, long long l, long long r, long long lz) {
if(t[p].l >= l && t[p].r <= r) {
long long res = lz * (t[p].r - t[p].l + 1);
// 计算懒标记的贡献。
return t[p].res + res;
}
lz += t[p].lz; // 累加懒标记。
long long mid = (t[p].l + t[p].r) >> 1, res = 0;
if(l <= mid) res += query(p << 1, l, r, lz);
if(r > mid) res += query(p << 1 | 1, l, r, lz);
return res;
}
标记永久化大概就是这样。
\(\text{hdu-1698}\)
给定一个长度为 \(n\) 的序列 \(a_i\),初始值为 \(1\)。有 \(q\) 次操作:
l r x将 \(a_l, a_{l+1}, \dots, a_r\) 都赋值为 \(x\)。
求 \(q\) 次操作后序列的元素和。
\(1 \le T \le 10\),\(1 \le n \le 10^5\),\(1 \le l \le r \le n\),\(1 \le x \le 3\)。
线段树维护即可,需要区间赋值、区间查询操作。
注意:多组数据,懒标记记得清空。
贴一下代码:
#include<iostream>
#include<cstdio>
using namespace std;
#define MAXN 100005
long long read() {
long long x = 0, f = 1;
char c = getchar();
while(c > 57 || c < 48) { if(c == 45) f = -1; c = getchar(); }
while(c >= 48 && c <= 57) { x = (x << 1) + (x << 3) + (c - 48); c = getchar(); }
return x * f;
}
struct node { long long l, r, res, lz; } t[MAXN << 2];
long long T, n, q, l, r, x, cnt;
void build(long long p, long long l, long long r) {
t[p].l = l, t[p].r = r, t[p].lz = 0;
if(l == r) { t[p].res = 1; return; }
long long mid = (l + r) >> 1;
build(p << 1, l, mid);
build(p << 1 | 1, mid + 1, r);
t[p].res = t[p << 1].res + t[p << 1 | 1].res;
return;
}
void pushdown(long long p) {
long long lz = t[p].lz;
t[p << 1].lz = t[p << 1 | 1].lz = lz;
t[p << 1].res = lz * (t[p << 1].r - t[p << 1].l + 1);
t[p << 1 | 1].res = lz * (t[p << 1 | 1].r - t[p << 1 | 1].l + 1);
t[p].lz = 0;
return;
}
void update(long long p, long long l, long long r, long long x) {
if(t[p].l >= l && t[p].r <= r) {
t[p].res = x * (t[p].r - t[p].l + 1);
t[p].lz = x; return;
}
if(t[p].lz) pushdown(p);
long long mid = (t[p].l + t[p].r) >> 1;
if(l <= mid) update(p << 1, l, r, x);
if(r > mid) update(p << 1 | 1, l, r, x);
t[p].res = t[p << 1].res + t[p << 1 | 1].res;
return;
}
int main() {
T = read();
while(T --) {
n = read(), q = read();
build(1, 1, n);
while(q --) {
l = read(), r = read(), x = read();
update(1, l, r, x);
}
cout << "Case " << (++ cnt) << ": The total value of the hook is " << t[1].res << ".\n";
}
return 0;
}
\(\text{uva-12299}\)
给定一个长度为 \(n\) 的序列 \(a_i\),有 \(m\) 次以下操作:
- 给定若干个下标 \(i_1,i_2,\dots,i_k\),让这些下标上的数向左循环移一位。
- 询问 \([l, r]\) 区间上的最小值。
\(1 \le n,q,a_i \le 10^5\),\(1 \le k \le 30\)。
由于 \(k\) 比较小,于是考虑将循环移位直接刻画为单点修改。维护区间 \(\min\) 即可。
注意:修改时应修改的是当前值,即 \(\text{query}(1,b_{i+1},b_{i+1})\),而非 \(a_{b_{i+1}}\)。
贴一下代码:
#include<iostream>
#include<cstdio>
using namespace std;
#define MAXN 100005
#define INF 0x3f3f3f3f
long long read() {
long long x = 0, f = 1;
char c = getchar();
while(c > 57 || c < 48) { if(c == 45) f = -1; c = getchar(); }
while(c >= 48 && c <= 57) { x = (x << 1) + (x << 3) + (c - 48); c = getchar(); }
return x * f;
}
struct node { long long l, r, minn; } t[MAXN << 2];
long long n, q, a[MAXN], b[MAXN];
string s;
void build(long long p, long long l, long long r) {
t[p].l = l, t[p].r = r;
if(l == r) { t[p].minn = a[l]; return; }
long long mid = (l + r) >> 1;
build(p << 1, l, mid);
build(p << 1 | 1, mid + 1, r);
t[p].minn = min(t[p << 1].minn, t[p << 1 | 1].minn);
return;
}
void update(long long p, long long x, long long k) {
if(t[p].l == t[p].r) { t[p].minn = k; return; }
long long mid = (t[p].l + t[p].r) >> 1;
if(x <= mid) update(p << 1, x, k);
else update(p << 1 | 1, x, k);
t[p].minn = min(t[p << 1].minn, t[p << 1 | 1].minn);
return;
}
long long query(long long p, long long l, long long r) {
if(t[p].l >= l && t[p].r <= r) return t[p].minn;
long long mid = (t[p].l + t[p].r) >> 1, res = INF;
if(l <= mid) res = min(res, query(p << 1, l, r));
if(r > mid) res = min(res, query(p << 1 | 1, l, r));
return res;
}
int main() {
n = read(), q = read();
for(int i = 1; i <= n; i ++) a[i] = read();
build(1, 1, n);
while(q --) {
cin >> s; long long cnt = 1;
for(int i = 1; i <= 30; i ++) b[i] = 0;
for(int i = 6; i < s.size(); i ++) {
if(s[i] == ')') continue;
if(s[i] == ',') cnt ++;
else b[cnt] = b[cnt] * 10 + (s[i] - '0');
}
if(s[0] == 'q') cout << query(1, b[1], b[2]) << "\n";
else {
long long res = query(1, b[1], b[1]);
for(int i = 1; i < cnt; i ++)
update(1, b[i], query(1, b[i + 1], b[i + 1]));
update(1, b[cnt], res);
}
}
return 0;
}
\(\text{uva-11992}\)
一个矩阵最多包含 \(10^6\) 个元素,分为 \(r\) 行和 \(c\) 列。其中的每个元素都有一个位置 \((x, y)\),其中 \(1 \le x \le r,1\le y\le c\)。最初,所有元素都为零。您需要处理\(3\)种操作:
-
\(1~x_1~y_1~x_2~y_2~v\):将子矩阵 \((x_1, y_1, x_2, y_2)\) 中的每个元素 \((x, y)\) 增加 \(v(v >0)\)。
-
\(2~x_1~y_1~x_2~y_2~v\):将子矩阵 \((x_1, y_1, x_2, y_2)\) 中的每个元素 \((x, y)\) 设置为 \(v\)。
-
\(3~x_1~y_1~x_2~y_2\):输出子矩阵 \((x_1,y_1,x_2,y_2)\) 中所有元素的总和、最小值和最大值。
在上面的描述中,子矩阵 \((x_1,y_1,x_2,y_2)\) 是指所有满足 \(x_1\le x\le x_2\) 和 \(y_1\le y\le y_2\) 的元素 \((x,y)\)。保证 \(1≤x_1≤x_2≤r,1≤y_1≤y_2≤c\)。任意操作后,矩阵中所有元素之和不超过\(10^9\)。
\(1 \le m \le 2 \times 10^4\),\(1 \le r \le 20\)。
注意到行数很小,所以很容易想到把二维转化成一维,那么修改子矩阵就可以暴力修改区间。
于是问题就变成区间加、区间赋值、区间查询最小值、最大值、总和。
需要注意 \(\text{pushdown}\) 的优先级,贴一下代码:
#include<iostream>
#include<cstdio>
using namespace std;
#define MAXN 1000005
#define INF 0x3f3f3f3f3f3f3f3f
long long read() {
long long x = 0, f = 1;
char c = getchar();
while(c > 57 || c < 48) { if(c == 45) f = -1; c = getchar(); }
while(c >= 48 && c <= 57) { x = (x << 1) + (x << 3) + (c - 48); c = getchar(); }
return x * f;
}
struct node { long long l, r, res, minn, maxn, lz1, lz2; } t[MAXN << 2];
long long n, m, q, op, xl, xr, yl, yr, w;
void build(long long p, long long l, long long r) {
t[p].l = l, t[p].r = r;
t[p].lz1 = 0, t[p].lz2 = INF;
t[p].res = t[p].minn = t[p].maxn = 0;
if(l == r) return;
long long mid = (l + r) >> 1;
build(p << 1, l, mid);
build(p << 1 | 1, mid + 1, r);
return;
}
void pushup(long long p) {
t[p].res = t[p << 1].res + t[p << 1 | 1].res;
t[p].minn = min(t[p << 1].minn, t[p << 1 | 1].minn);
t[p].maxn = max(t[p << 1].maxn, t[p << 1 | 1].maxn);
return;
}
void upd(long long p, long long add, long long mk) {
if(mk == INF) {
t[p].res += add * (t[p].r - t[p].l + 1);
t[p].minn += add, t[p].maxn += add;
t[p].lz1 += add;
}
else {
t[p].res = (mk + add) * (t[p].r - t[p].l + 1);
t[p].minn = t[p].maxn = mk + add;
t[p].lz1 = add, t[p].lz2 = mk;
}
return;
}
void pushdown(long long p) {
upd(p << 1, t[p].lz1, t[p].lz2);
upd(p << 1 | 1, t[p].lz1, t[p].lz2);
t[p].lz1 = 0, t[p].lz2 = INF;
return;
}
void update(long long p, long long l, long long r, long long add, long long mk) {
if(t[p].l >= l && t[p].r <= r) { upd(p, add, mk); return; }
long long mid = (t[p].l + t[p].r) >> 1; pushdown(p);
if(l <= mid) update(p << 1, l, r, add, mk);
if(r > mid) update(p << 1 | 1, l, r, add, mk);
pushup(p);
return;
}
long long query1(long long p, long long l, long long r) {
if(t[p].l >= l && t[p].r <= r) return t[p].res;
long long mid = (t[p].l + t[p].r) >> 1, res = 0;
pushdown(p);
if(l <= mid) res += query1(p << 1, l, r);
if(r > mid) res += query1(p << 1 | 1, l, r);
pushup(p); return res;
}
long long query2(long long p, long long l, long long r) {
if(t[p].l >= l && t[p].r <= r) return t[p].minn;
long long mid = (t[p].l + t[p].r) >> 1, res = INF;
pushdown(p);
if(l <= mid) res = min(res, query2(p << 1, l, r));
if(r > mid) res = min(res, query2(p << 1 | 1, l, r));
pushup(p); return res;
}
long long query3(long long p, long long l, long long r) {
if(t[p].l >= l && t[p].r <= r) return t[p].maxn;
long long mid = (t[p].l + t[p].r) >> 1, res = -INF;
pushdown(p);
if(l <= mid) res = max(res, query3(p << 1, l, r));
if(r > mid) res = max(res, query3(p << 1 | 1, l, r));
pushup(p); return res;
}
int main() {
while(cin >> n >> m >> q) {
build(1, 1, n * m);
while(q --) {
op = read(), xl = read(), yl = read();
xr = read(), yr = read();
if(op == 1) {
w = read();
for(int i = xl - 1; i < xr; i ++)
update(1, i * m + yl, i * m + yr, w, INF);
}
else if(op == 2) {
w = read();
for(int i = xl - 1; i < xr; i ++)
update(1, i * m + yl, i * m + yr, 0, w);
}
else {
long long res = 0, minn = INF, maxn = -INF;
for(int i = xl - 1; i < xr; i ++)
res += query1(1, i * m + yl, i * m + yr),
minn = min(minn, query2(1, i * m + yl, i * m + yr)),
maxn = max(maxn, query3(1, i * m + yl, i * m + yr));
cout << res << " " << minn << " " << maxn << "\n";
}
}
}
return 0;
}
\(\text{loj-10116}\)
给定 \(n,k\),表示火车共有 \(n\) 节车厢以及 \(k\) 个事件。
接下来给出 \(k\) 个事件,每行开头都有一个字母 A,B 或 C。
- 如果字母为
A,接下来是一个数 \(m\),表示年级主任现在在第 \(m\) 节车厢; - 如果字母为
B,接下来是两个数 \(m,p\),表示在第 \(m\) 节车厢有 \(p\) 名学生上车; - 如果字母为
C,接下来是两个数 \(m,p\),表示在第 \(m\) 节车厢有 \(p\) 名学生下车。
学生总人数不会超过 \(10^5\)。
\(1 \le n \le 5 \times 10^5\),\(1 \le k \le 10^5\),至少有 \(3 \times 10^4\) 个 A。
可以用权值线段树去做,不需要 \(\text{build}\)。贴一下代码:
#include<iostream>
#include<cstdio>
using namespace std;
#define MAXN 500005
long long read() {
long long x = 0, f = 1;
char c = getchar();
while(c > 57 || c < 48) { if(c == 45) f = -1; c = getchar(); }
while(c >= 48 && c <= 57) { x = (x << 1) + (x << 3) + (c - 48); c = getchar(); }
return x * f;
}
long long n, k, t[MAXN << 2], x, y;
char op;
void update(long long p, long long l, long long r, long long x, long long y) {
if(l == r) { t[p] += y; return; }
long long mid = (l + r) >> 1;
if(x <= mid) update(p << 1, l, mid, x, y);
else update(p << 1 | 1, mid + 1, r, x, y);
t[p] = t[p << 1] + t[p << 1 | 1];
return;
}
long long query(long long p, long long l, long long r, long long pl, long long pr) {
if(l >= pl && r <= pr) return t[p];
long long mid = (l + r) >> 1, res = 0;
if(pl <= mid) res += query(p << 1, l, mid, pl, pr);
if(pr > mid) res += query(p << 1 | 1, mid + 1, r, pl, pr);
return res;
}
int main() {
n = read(), k = read();
while(k --) {
cin >> op;
if(op == 'A') x = read(), cout << query(1, 1, n, 1, x) << "\n";
else if(op == 'B') x = read(), y = read(), update(1, 1, n, x, y);
else x = read(), y = read(), update(1, 1, n, x, -y);
}
return 0;
}
\(\text{zkw }\)线段树
\(\text{zkw}\) 线段树是一种用循环实现的线段树,比正常的递归式线段树快很多,而且好写。
线段树的叶子节点的下标是连续的,因此叶子节点的编号与原数组的编号有一种对应关系。
简单观察一下就会发现他们的编号差是个定值,即除去叶子节点之外的节点数量。
为了方便,我们约定,无论树有没有那么大,我们都把差值看作 \(n\),无数据的叶节点空置即可。
这样就可以用循环建树:
void build() {
n = read(), m = read();
for(int i = n + 1; i <= (n << 1); i ++) t[i] = read();
for(int i = n - 1; i >= 1; i --) t[i] = t[i << 1] + t[i << 1 | 1];
return;
}
对于单点修改,我们直接自底向上更新即可:
void update1(long long x, long long k) {
for(int i = x + n; i >= 1; i /= 2) t[i] += k;
return;
}
单点查询就更简单了:
long long query1(long long x) { return t[x + n]; }
那么怎么区间查询呢?我们搞两个指针 \(pl = l - 1, pr = r + 1\)。
让 \(pl, pr\) 一直往上跳,直到他们的父亲节点相同,此时这两个指针路径围着的部分就是查询区间。
发现一个规律:
- 若 \(pl\) 当前是父亲节点的左儿子,那么右儿子一定在答案中。
- 若 \(pr\) 当前是父亲节点的右儿子,那么左儿子一定在答案中。
这样就可以用循环区间查询了:
long long query(long long l, long long r) {
long long res = 0, pl = l - 1, pr = r + 1;
while(pl / 2 != pr / 2) {
if(!(pl & 1)) res += t[pl + 1];
if(pr & 1) res += t[pr - 1];
pl /= 2, pr /= 2;
}
return res;
}
但是 \(\text{zkw}\) 线段树要求 \(n\) 是 \(2\) 的次幂,虽然不是也可以调整吧,但感觉没什么太大用。
区间修改和单点查询就不写了,感觉还不如常规线段树。
\(\text{uva-10587}\)
有一个长度为 \(10^7\) 的序列 \(a_i\),和 \(n\) 个区间 \([l_i, r_i]\),表示依次将 \([l_i, r_i]\) 上的数设为 \(i\)。
求操作完后序列中有多少不同的数。
\(1 \le n \le 10^4\),\(1 \le l_i \le r_i \le 10^7\)。
这是一道权值线段树的题,但维护值域为 \([1, 10^7]\) 的线段树显然不现实。
考虑离散化,但这个不能直接离散化,否则会出现一些边界问题,反例就不举了。
将 \(l_i,r_i,r_i+1\) 都加进离散化数组里即可。
然后对于这道题的查询是这样的:
void query(long long p) {
if(t[p].res != -1) {
if(!mp[t[p].res]) mp[t[p].res] = 1, ans ++;
return;
}
long long mid = (t[p].l + t[p].r) >> 1;
query(p << 1), query(p << 1 | 1);
return;
}
这样就统计了多少个不同的数。
\(\text{loj-10128}\)
给定长度为 \(n\) 的序列 \(a_i\),有 \(q\) 次以下两种操作:
1 l r区间查询操作,输出 \(\sum\limits_{i=l}^r a_i\) 的值。2 l r区间开根操作,对于 \(i \in [l,r],a_i \gets \sqrt a_i\),下取整。
\(1 \le n \le 10^5\),\(1 \le q \le 2 \times 10^5\),\(1 \le l \le r \le n\),\(0 \le a_i \le 10^9\)。
开根操作比较特殊,对于 \(10^9\) 范围内的数,最多开 \(8\) 次根就会变成 \(0/1\)。
因此若一个区间内最大值为 \(0/1\),则这个区间可以跳过不操作,因为操作无变化。
我们也不需要考虑懒标记,开根操作可以直接更新到叶子节点,因为每个数最多操作 \(8\) 次。
时间复杂度为单次操作 \(O(\log n)\),需要维护区间最大值、区间和。
#include<iostream>
#include<cstdio>
#include<cmath>
using namespace std;
#define MAXN 100005
long long read() {
long long x = 0, f = 1;
char c = getchar();
while(c > 57 || c < 48) { if(c == 45) f = -1; c = getchar(); }
while(c >= 48 && c <= 57) { x = (x << 1) + (x << 3) + (c - 48); c = getchar(); }
return x * f;
}
struct node { long long l, r, w, mx; } t[MAXN << 2];
long long n, m, a[MAXN];
void pushup(long long p) {
t[p].w = t[p << 1].w + t[p << 1 | 1].w;
t[p].mx = max(t[p << 1].mx, t[p << 1 | 1].mx);
return;
}
void build(long long p, long long l, long long r) {
t[p].l = l, t[p].r = r;
if(l == r) { t[p].w = t[p].mx = a[l]; return; }
long long mid = (l + r) >> 1;
build(p << 1, l, mid);
build(p << 1 | 1, mid + 1, r);
pushup(p); return;
}
void upd(long long p, long long l, long long r) {
if(t[p].mx <= 1) return;
if(t[p].l == t[p].r) { t[p].w = t[p].mx = sqrt(t[p].w); return; }
long long mid = (t[p].l + t[p].r) >> 1;
if(l <= mid) upd(p << 1, l, r);
if(r > mid) upd(p << 1 | 1, l, r);
pushup(p); return;
}
long long qry(long long p, long long l, long long r) {
if(l <= t[p].l && t[p].r <= r) return t[p].w;
long long mid = (t[p].l + t[p].r) >> 1, res = 0;
if(l <= mid) res += qry(p << 1, l, r);
if(r > mid) res += qry(p << 1 | 1, l, r);
return res;
}
int main() {
n = read();
for(int i = 1; i <= n; i ++) a[i] = read();
build(1, 1, n), m = read();
while(m --) {
long long x = read(), l = read(), r = read();
if(x == 2) upd(1, l, r);
else cout << qry(1, l, r) << "\n";
}
return 0;
}
\(\text{hdu-3308}\)
给定长度为 \(n\) 的序列 \(a_i\),有 \(q\) 次以下两种操作:
U x k替换操作,\(a_x \gets k\)。Q l r查询操作,查询 \([l,r]\) 区间内最长连续严格递增子序列的长度。
注意,本题输入下标从 \(0\) 开始。
\(1 \le n,q \le 10^5\),\(0 \le a_i \le 10^5\)。
区间合并经典题。只需要考虑如何向上 pushup。
其实也很好处理,记录当前节点分别从左端/右端开始的最长子序列长度,和总长度即可。
pushup 时注意处理区间合并的关系。
#include<iostream>
#include<cstdio>
#include<cmath>
using namespace std;
#define MAXN 100005
long long read() {
long long x = 0, f = 1;
char c = getchar();
while(c > 57 || c < 48) { if(c == 45) f = -1; c = getchar(); }
while(c >= 48 && c <= 57) { x = (x << 1) + (x << 3) + (c - 48); c = getchar(); }
return x * f;
}
struct node { long long l, r, ls, rs, mx; } t[MAXN << 2];
long long T, n, q, a[MAXN];
void pushup(long long p) {
t[p].ls = t[p << 1].ls, t[p].rs = t[p << 1 | 1].rs;
t[p].mx = max(t[p << 1].mx, t[p << 1 | 1].mx);
long long mid = (t[p].l + t[p].r) >> 1;
if(a[mid] < a[mid + 1]) {
if(t[p << 1].ls == t[p << 1].r - t[p << 1].l + 1)
t[p].ls += t[p << 1 | 1].ls;
if(t[p << 1 | 1].rs == t[p << 1 | 1].r - t[p << 1 | 1].l + 1)
t[p].rs += t[p << 1].rs;
t[p].mx = max(t[p].mx, t[p << 1].rs + t[p << 1 | 1].ls);
}
return;
}
void build(long long p, long long l, long long r) {
t[p].l = l, t[p].r = r, t[p].ls = t[p].rs = t[p].mx = 0;
if(l == r) { t[p].ls = t[p].rs = t[p].mx = 1; return; }
long long mid = (l + r) >> 1;
build(p << 1, l, mid);
build(p << 1 | 1, mid + 1, r);
pushup(p); return;
}
void upd(long long p, long long x) {
if(t[p].l == t[p].r) return;
long long mid = (t[p].l + t[p].r) >> 1;
if(x <= mid) upd(p << 1, x);
else upd(p << 1 | 1, x);
pushup(p); return;
}
long long qry(long long p, long long l, long long r) {
if(l <= t[p].l && t[p].r <= r) return t[p].mx;
long long mid = (t[p].l + t[p].r) >> 1, res = 0;
if(r <= mid) res = max(res, qry(p << 1, l, r));
else if(l > mid) res = max(res, qry(p << 1 | 1, l, r));
else {
res = max(res, qry(p << 1, l, r));
res = max(res, qry(p << 1 | 1, l, r));
if(a[mid] < a[mid + 1]) res = max(res, min(mid - l + 1,
t[p << 1].rs) + min(r - mid, t[p << 1 | 1].ls));
}
return res;
}
int main() {
T = read();
while(T --) {
n = read(), q = read();
for(int i = 1; i <= n; i ++) a[i] = read();
build(1, 1, n);
while(q --) {
char c; cin >> c;
long long x = read() + 1, k = read();
if(c == 'U') a[x] = k, upd(1, x);
else cout << qry(1, x, k + 1) << "\n";
}
}
return 0;
}
\(\text{uva-1400}\)
给定长度为 \(n\) 的序列 \(a_i\),有 \(q\) 次询问,每次询问给定 \(l,r\),求 \([l,r]\) 中哪一段连续区间和最大。
多个答案时输出起点最小的那个,若仍有多个答案,输出终点最小的那个。
\(1 \le n,q \le 5 \times 10^5\)。
跟上一题的区间合并操作处理方式一致,只不过需要记录答案而已。
\(\text{hdu-1540}\)
给定长度为 \(n\) 的 \(01\) 序列 \(a_i\),初始时全为 \(1\)。有 \(q\) 次以下三种操作:
D x将 \(a_x\) 设为 \(0\)。Q x查询与 \(a_x\) 相连的 \(1\) 的个数,即包含 \(x\) 的全 \(1\) 区间的长度。R撤销最近的D操作。
注意,本题包含多组测试样例。
\(1 \le n,q \le 5 \times 10^4\)。
本质上还是和区间合并问题差不多。
记录每个区间分别从左端/右端开始的最长的全 \(1\) 区间长度。
只需要稍微改一下查询方式即可。
至于撤销操作,可以用栈记录下每次 D 操作。
注意:本题多测,题面没说,太坑人了 \(\text{hdu}\)。
#include<iostream>
#include<cstdio>
using namespace std;
#define MAXN 50005
long long read() {
long long x = 0, f = 1;
char c = getchar();
while(c > 57 || c < 48) { if(c == 45) f = -1; c = getchar(); }
while(c >= 48 && c <= 57) { x = (x << 1) + (x << 3) + (c - 48); c = getchar(); }
return x * f;
}
struct node { long long l, r, ls, rs; } t[MAXN << 2];
long long n, q, s[MAXN], top;
void pushup(long long p) {
t[p].ls = t[p << 1].ls, t[p].rs = t[p << 1 | 1].rs;
if(t[p << 1].ls == t[p << 1].r - t[p << 1].l + 1)
t[p].ls += t[p << 1 | 1].ls;
if(t[p << 1 | 1].rs == t[p << 1 | 1].r - t[p << 1 | 1].l + 1)
t[p].rs += t[p << 1].rs;
return;
}
void build(long long p, long long l, long long r) {
t[p].l = l, t[p].r = r;
if(l == r) { t[p].ls = t[p].rs = 1; return; }
long long mid = (l + r) >> 1;
build(p << 1, l, mid);
build(p << 1 | 1, mid + 1, r);
pushup(p); return;
}
void upd(long long p, long long x, long long k) {
if(t[p].l == t[p].r) { t[p].ls = t[p].rs = k; return; }
long long mid = (t[p].l + t[p].r) >> 1;
if(x <= mid) upd(p << 1, x, k);
else upd(p << 1 | 1, x, k);
pushup(p); return;
}
long long qry(long long p, long long x) {
if(t[p].l == t[p].r) return t[p].ls;
long long mid = (t[p].l + t[p].r) >> 1;
if(x <= mid) {
if(t[p << 1].r - x < t[p << 1].rs)
return t[p << 1].rs + t[p << 1 | 1].ls;
return qry(p << 1, x);
}
else if(x - t[p << 1].r <= t[p << 1 | 1].ls)
return t[p << 1].rs + t[p << 1 | 1].ls;
return qry(p << 1 | 1, x);
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
while(cin >> n >> q) {
build(1, 1, n);
while(q --) {
char c; cin >> c; long long x;
if(c == 'D') cin >> x, upd(1, x, 0), s[++ top] = x;
else if(c == 'Q') cin >> x, cout << qry(1, x) << "\n";
else upd(1, s[top --], 1);
}
}
return 0;
}
\(\text{hdu-4614}\)
爱丽丝人气爆棚,每天都能收到一大堆鲜花。她有 \(n\) 个花瓶,编号 \(0 \sim n-1\)。
每当收到花时,她会尝试把花放进花瓶里,一朵花一个花瓶。
她会选一个花瓶 \(x\),先试着往花瓶 \(x\) 里放花。如果花瓶里没花,就放进去;有花的话就跳过。然后依次尝试花瓶 \(x+1 \sim n-1\),直到花放完或者试到最后一个花瓶。剩下没放进去的花就只能扔掉了。
当然,有时候她也会清理花瓶。因为花瓶太多,她会选一段编号 \([l,r]\) 的花瓶清理,清理时这些花瓶里的花都会被丢弃。
\(2 \le n,m \le 5 \times 10^4\)。
用线段树维护一段区间内的空瓶子个数,懒标记区间内是否所有的瓶子都为空。
操作 \(2\) 很简单,就是一个简单的区间查询,首先查询出 \([l,r]\) 区间内的空瓶子个数,然后用总的区间长度减去空瓶子个数就是丢弃的花的个数,然后我们再把 \([l,r]\) 区间的非空瓶子都变成空的,一个简单的区间修改。
对于操作 \(1\) 而言,我们首先查询出区间 \([x,n]\) 区间的空瓶子个数,如果为 \(0\) 那么直接输出;否则我们需要二分去求出 \([x,n]\) 区间内的第一个空瓶子位置和最后一个空瓶子位置。然后我们就可以在 \([x,n]\) 区间去二分枚举空瓶子数等于 \(1\) 的位置,以及空瓶子数为 $\min \((花朵的数量、\)[x,n]$ 区间内的空瓶子个数)的位置,再把这两个位置之间的空瓶都处理成非空的。
\(\text{spoj-GSS1}\)
给定长度为 \(n\) 的序列 \(a\),有 \(q\) 次查询,每次查询给定 \([l,r]\),你需要给出下式的值:
\(1 \le n \le 5 \times 10^4\),\(|a_i| \le 15007\)。
虽然是静态无修的,但也考虑用线段树维护。
其实比较板子的,线段树维护 \(lmx,rmx,mx,res\),分别表示区间左端开始的最大区间和、右端开始的最大区间和、整个区间的最大区间和以及区间和。
上传时大概就是下式:
其他就没什么注意的点了。
#include<iostream>
#include<cstdio>
using namespace std;
#define ll long long
#define MAXN 1000005
ll read() {
ll x = 0, f = 1;
char c = getchar();
while(c < 48 || c > 57) { if(c == 45) f = -1; c = getchar(); }
while(c >= 48 && c <= 57) { x = (x << 3) + (x << 1) + (c - 48); c = getchar(); }
return x * f;
}
struct node { ll l, r, lmx, rmx, mx, res; } t[MAXN << 2];
ll n, m, a[MAXN];
void calc(node &p, node &l, node &r) {
p.res = l.res + r.res;
p.lmx = max(l.lmx, l.res + r.lmx);
p.rmx = max(r.rmx, r.res + l.rmx);
p.mx = max(l.rmx + r.lmx, max(l.mx, r.mx));
return;
}
void build(ll p, ll l, ll r) {
t[p].l = l, t[p].r = r;
if(l == r) {
t[p].lmx = t[p].rmx = t[p].mx = t[p].res = a[l];
return;
}
ll mid = (l + r) >> 1;
build(p << 1, l, mid), build(p << 1, mid + 1, r);
calc(t[p], t[p << 1], t[p << 1 | 1]);
return;
}
node qry(ll p, ll l, ll r) {
if(l <= t[p].l && t[p].r <= r) return t[p];
ll mid = (t[p].l + t[p].r) >> 1;
if(l > mid) return qry(p << 1 | 1, l, r);
if(r <= mid) return qry(p << 1, l, r);
node x = qry(p << 1, l, r), y = qry(p << 1 | 1, l, r), res;
calc(res, x, y); return res;
}
int main() {
n = read();
for(int i = 1; i <= n; i ++) a[i] = read();
m = read(), build(1, 1, n);
while(m --) {
ll x = read(), y = read();
cout << qry(1, x, y).mx << "\n";
}
return 0;
}
本文来自博客园,作者:So_noSlack,转载请注明原文链接:https://www.cnblogs.com/So-noSlack/p/19219613

浙公网安备 33010602011771号