目录
- 题单
- 线段树1
- TJOI2009开关
- 逆序对
- 忠诚
- 无聊的数列
- 扶苏的问题
- 线段树2
- [COCI2010-2011#6]STEP
- 三元上升子序列
- 色板游戏
- 小白逛公园
- 方差
- [yLOI2019]棠梨煎雪
- 上帝造题的七分钟2/花神游历各国
- [SCOI2010]序列操作
- Points
题单
线段树1
区间相加,区间求和
#include <bits/stdc++.h>
using namespace std;
const int N = 100010;
typedef long long LL;
int n, m;
int w[N];
struct Node {
int l, r;
LL sum, add;
}tr[N << 2];
void pushup(int u) {
tr[u].sum = tr[u << 1].sum + tr[u << 1 | 1].sum;
}
void pushdown(int u) {
auto& root = tr[u], &left = tr[u << 1], &right = tr[u << 1 | 1];
if (root.add) {
left.add += root.add, left.sum += (LL)(left.r - left.l + 1) * root.add;
right.add += root.add, right.sum += (LL)(right.r - right.l + 1) * root.add;
root.add = 0;
}
}
void build(int u, int l, int r) {
if (l == r) tr[u] = {l, r, w[l], 0};
else {
tr[u] = {l, r};
int mid = l + r >> 1;
build(u << 1, l, mid);
build(u << 1 | 1, mid + 1, r);
// 注意这里
pushup(u);
}
}
void modify(int u, int l, int r, int d) {
if (l <= tr[u].l && r >= tr[u].r) {
// [l, r]完全包含u对应的区间
// 处理sum和懒标记
tr[u].sum += (LL)d * (tr[u].r - tr[u].l + 1);
tr[u].add += d;
} else {
// 注意这里
pushdown(u);
int mid = tr[u].l + tr[u].r >> 1;
if (l <= mid) modify(u << 1, l, r, d);
if (r > mid) modify(u << 1 | 1, l, r, d);
// 注意这里
pushup(u);
}
}
LL query(int u, int l, int r) {
if (l <= tr[u].l && r >= tr[u].r) return tr[u].sum;
// 注意这里
pushdown(u);
int mid = tr[u].l + tr[u].r >> 1;
LL sum = 0;
if (l <= mid) sum += query(u << 1, l, r);
if (r > mid) sum += query(u << 1 | 1, l, r);
return sum;
}
int main() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++) scanf("%d", w + i);
build(1, 1, n);
int op, l, r, d;
while (m--) {
scanf("%d", &op);
if (op == 1) {
scanf("%d%d%d", &l, &r, &d);
modify(1, l, r, d);
} else {
scanf("%d%d", &l, &r);
printf("%lld\n", query(1, l, r));
}
}
return 0;
}
TJOI2009开关
一排灯初始是关闭的,每次可翻转一个区间的等的状态,每次询问一个区间内亮的等的个数。相当于是区间改状态,区间求和。
sum表示当前节点的开着的灯的个数,tag表示操作了多少次.
#include <bits/stdc++.h>
using namespace std;
#ifdef LOCAL
#include "debug.h"
#else
#define debug(...) 42
#endif
const int N = 100010;
typedef long long LL;
int n, m;
int w[N];
struct Node {
int l, r;
// sum当前区间开着的灯的个数 tag标记:初始为0,操作一次变为1
int sum, tag;
}tr[N << 2];
void pushup(int u) {
tr[u].sum = tr[u << 1].sum + tr[u << 1 | 1].sum;
}
void pushdown(int u) {
auto& root = tr[u], &left = tr[u << 1], &right = tr[u << 1 | 1];
if (root.tag) {
left.tag ^=1, left.sum = (left.r - left.l + 1)- left.sum;
right.tag ^= 1, right.sum = (right.r - right.l + 1) - right.sum;
root.tag = 0;
}
}
void build(int u, int l, int r) {
if (l == r) tr[u] = {l, r, 0, 0};
else {
tr[u] = {l, r};
int mid = l + r >> 1;
build(u << 1, l, mid);
build(u << 1 | 1, mid + 1, r);
// 注意这里
pushup(u);
}
}
void modify(int u, int l, int r) {
if (l <= tr[u].l && r >= tr[u].r) {
// [l, r]完全包含u对应的区间
// 处理sum和懒标记
tr[u].sum = (tr[u].r - tr[u].l + 1) - tr[u].sum;
tr[u].tag ^= 1;
} else {
// 注意这里
pushdown(u);
int mid = tr[u].l + tr[u].r >> 1;
if (l <= mid) modify(u << 1, l, r);
if (r > mid) modify(u << 1 | 1, l, r);
// 注意这里
pushup(u);
}
}
LL query(int u, int l, int r) {
if (l <= tr[u].l && r >= tr[u].r) {
// debug(tr[u].l, tr[u].r, tr[u].sum);
return tr[u].sum;
}
// 注意这里
pushdown(u);
int mid = tr[u].l + tr[u].r >> 1;
LL sum = 0;
if (l <= mid) sum += query(u << 1, l, r);
if (r > mid) sum += query(u << 1 | 1, l, r);
return sum;
}
int main() {
scanf("%d%d", &n, &m);
build(1, 1, n);
int op, l, r, d;
while (m--) {
scanf("%d", &op);
if (op == 0) {
scanf("%d%d", &l, &r);
modify(1, l, r);
} else {
scanf("%d%d", &l, &r);
printf("%lld\n", query(1, l, r));
}
}
return 0;
}
逆序对
忠诚
区间求最小值.
#include <bits/stdc++.h>
using namespace std;
#ifdef LOCAL
#include "debug.h"
#else
#define debug(...) 42
#endif
typedef long long LL;
const int N = 200010;
struct Node
{
int l, r;
int v; // 区间[l, r]中的最小值
}tr[N * 4];
int w[N];
void pushup(int u) // 由子节点的信息,来计算父节点的信息
{
tr[u].v = min(tr[u << 1].v, tr[u << 1 | 1].v);
}
void build(int u, int l, int r)
{
if (l == r) tr[u] = {l, r, w[l]};
else {
tr[u] = {l, r};
int mid = l + r >> 1;
build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);
pushup(u);
}
}
int query(int u, int l, int r)
{
if (tr[u].l >= l && tr[u].r <= r) return tr[u].v; // 树中节点,已经被完全包含在[l, r]中了
int mid = tr[u].l + tr[u].r >> 1;
int v = INT_MAX;
if (l <= mid) v = query(u << 1, l, r);
if (r > mid) v = min(v, query(u << 1 | 1, l, r));
return v;
}
// 本题不需要这个函数,这个函数是单点修改
void modify(int u, int x, int v)
{
if (tr[u].l == x && tr[u].r == x) tr[u].v = v;
else
{
int mid = tr[u].l + tr[u].r >> 1;
if (x <= mid) modify(u << 1, x, v);
else modify(u << 1 | 1, x, v);
pushup(u);
}
}
int main()
{
int m, n;
scanf("%d%d", &m, &n);
for (int i = 1; i <= m; i++) scanf("%d", w + i);
build(1, 1, m);
int x, y;
while (n--)
{
scanf("%d%d", &x, &y);
printf("%d ", query(1, x, y));
}
return 0;
}
无聊的数列
区间加等差数列。
转化为维护原序列的差分数列,那么区间
[
l
,
r
]
[l,r]
[l,r]加等差数列等价于在差分数列的[l]加首项,
[
l
+
1
,
r
]
[l+1, r]
[l+1,r]加公差,
[
r
]
[r]
[r]处减去末项. 转化为区间修改,区间求和的问题。
#include <bits/stdc++.h>
using namespace std;
const int N = 100010;
typedef long long LL;
int n, m;
int w[N];
struct Node {
int l, r;
LL sum, add;
}tr[N << 2];
void pushup(int u) {
tr[u].sum = tr[u << 1].sum + tr[u << 1 | 1].sum;
}
void pushdown(int u) {
auto& root = tr[u], &left = tr[u << 1], &right = tr[u << 1 | 1];
if (root.add) {
left.add += root.add, left.sum += (LL)(left.r - left.l + 1) * root.add;
right.add += root.add, right.sum += (LL)(right.r - right.l + 1) * root.add;
root.add = 0;
}
}
void build(int u, int l, int r) {
if (l == r) tr[u] = {l, r, w[l], 0};
else {
tr[u] = {l, r};
int mid = l + r >> 1;
build(u << 1, l, mid);
build(u << 1 | 1, mid + 1, r);
// 注意这里
pushup(u);
}
}
void modify(int u, int l, int r, int d) {
if (l <= tr[u].l && r >= tr[u].r) {
// [l, r]完全包含u对应的区间
// 处理sum和懒标记
tr[u].sum += (LL)d * (tr[u].r - tr[u].l + 1);
tr[u].add += d;
} else {
// 注意这里
pushdown(u);
int mid = tr[u].l + tr[u].r >> 1;
if (l <= mid) modify(u << 1, l, r, d);
if (r > mid) modify(u << 1 | 1, l, r, d);
// 注意这里
pushup(u);
}
}
LL query(int u, int l, int r) {
if (l <= tr[u].l && r >= tr[u].r) return tr[u].sum;
// 注意这里
pushdown(u);
int mid = tr[u].l + tr[u].r >> 1;
LL sum = 0;
if (l <= mid) sum += query(u << 1, l, r);
if (r > mid) sum += query(u << 1 | 1, l, r);
return sum;
}
int main() {
scanf("%d%d", &n, &m);
//原数组
for (int i = 1; i <= n; i++) scanf("%d", w + i);
// 差分数组
for (int i = n; i > 1; i--) w[i] = w[i] - w[i - 1];
build(1, 1, n);
int op, l, r, k, d;
while (m--) {
scanf("%d", &op);
if (op == 1) {
scanf("%d%d%d%d", &l, &r, &k, &d);
// 差分数组 w[l] += k
modify(1, l, l, k);
// 差分数组 [l+1, r] += d
if (l + 1 <= r) modify(1, l + 1, r, d);
// 差分数组 w[r+1]-=末项 也就是 k+d*(r-l)
if (r < n) modify(1, r + 1, r + 1, -(k + 1LL * d * (r - l)));
} else {
scanf("%d", &r);
printf("%lld\n", query(1, 1, r));
}
}
return 0;
}
扶苏的问题
支持区间加,区间修改,询问区间最大值
做法:打两个标记
线段树2
支持区间加,区间乘,求区间和
做法:打两个标记
[COCI2010-2011#6]STEP
01序列,支持单点修改一个点,询问区间内的最长无相相邻数字连续串的长度
三元上升子序列
询问 i < j < k 且 a [ i ] < a [ j ] < a [ k ] i<j<k且a[i]<a[j]<a[k] i<j<k且a[i]<a[j]<a[k]的序列个数
色板游戏
支持区间修改,询问区间内不同颜色个数。颜色个数 <= 30
小白逛公园
支持单点修改,询问区间内最长连续区间最大和
方差
支持区间加,询问区间平均数,询问区间方差
[yLOI2019]棠梨煎雪
m个01传,可能有?字符。支持单点修改,区间查询
上帝造题的七分钟2/花神游历各国
支持区间开平方,询问区间和
[SCOI2010]序列操作
给一个01序列,支持区间修改为0或1,区间取反,询问区间1的个数,询问区间最多连续1的个数
Points
二维坐标系,支持添加点,删除点,询问一个点最左边的点的坐标