基本操作
支持单点更新、区间查询等等操作
凡是树状数组能做的,线段树都能做
牛客一个小栗子Can you answer these queries III
#pragma GCC optimize(3, "Ofast", "inline")
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
typedef pair<long long, long long> pll;
const int N = 5e5 + 100;
const int M = 2e6 + 1000;
const int inf = 0x3f3f3f3f;
const int mod = (1 << 31) - 1;
#define IOS \
ios::sync_with_stdio(0); \
cin.tie(0); \
cout.tie(0);
ll n, k, m, x, y;
ll a[N];
struct tree
{
ll l, r;
ll sum, lsum, rsum, ans;
} t[N * 4];
void update(tree &w, tree &l, tree &r)//
{
w.sum = l.sum + r.sum;
w.lsum = max(l.lsum, l.sum + r.lsum); //
w.rsum = max(r.rsum, r.sum + l.rsum);
w.ans = max(l.rsum + r.lsum, max(l.ans, r.ans));
}
void build(ll w, ll l, ll r)
{
if (l == r)
t[w] = {l, r, a[l], a[l], a[l], a[l]};
else
{
t[w] = {l, r};
ll mid = l + r >> 1;
build(w << 1, l, mid);
build(w << 1 | 1, mid + 1, r);
update(t[w], t[w << 1], t[w << 1 | 1]); //
}
}
void calc(ll w, ll x, ll v)
{
if (t[w].l == x && t[w].r == x)
t[w] = {x, x, v, v, v, v};
else
{
ll mid = t[w].l + t[w].r >> 1;
if (x <= mid)
calc(w << 1, x, v);
else
calc(w << 1 | 1, x, v);
update(t[w], t[w << 1], t[w << 1 | 1]);
}
}
tree query(ll w, ll l, ll r)
{
if (t[w].l >= l && t[w].r <= r)
return t[w];
else
{
ll mid = t[w].l + t[w].r >> 1;
if (r <= mid)
return query(w << 1, l, r);
else if (mid < l)
return query(w << 1 | 1, l, r);
else
{
tree L = query(w << 1, l, r);
tree R = query(w << 1 | 1, l, r);
tree res;
update(res, L, R);
return res;
}
}
}
int main()
{
// IOS;
scanf("%lld%lld", &n, &m);
for (int i = 1; i <= n; i++)
scanf("%lld", &a[i]);
build(1, 1, n); //
while (m--)
{
scanf("%lld%lld%lld", &k, &x, &y);
if (k == 1)
{
if (x > y)
swap(x, y);
printf("%lld\n", query(1, x, y).ans);
}
else if (k == 2)
calc(1, x, y);
}
return 0;
}
牛客一道小栗子interval GCD
#pragma GCC optimize(3, "Ofast", "inline")
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
typedef pair<long long, long long> pll;
const int N = 500000;
const int M = 2e6 + 1000;
const int inf = 0x3f3f3f3f;
const int mod = (1 << 31) - 1;
#define IOS \
ios::sync_with_stdio(0); \
cin.tie(0); \
cout.tie(0);
ll n, m, l, r, d;
ll a[N], b[N], c[N];
struct wt
{
ll l, r, dat;
} t[N * 4];//4倍保证不越界
ll gcd(ll x, ll y) { return y == 0 ? x : gcd(y, x % y); }
ll lowbit(ll x) { return x & -x; }
void add(ll x, ll val)
{
for (x; x <= n; x += lowbit(x))
c[x] += val;
}
ll getsum(ll x)
{
ll res = 0;
for (; x; x -= lowbit(x))
res += c[x];
return res;
}
void build(ll u, ll l, ll r)//建树
{
t[u].l = l, t[u].r = r;
if (l == r)
{
t[u].dat = b[r]; //
return;
}
ll mid = l + r >> 1; //建树有问题
build(u * 2, l, mid);
build(u * 2 + 1, mid + 1, r);
t[u].dat = gcd(t[u * 2].dat, t[u * 2 + 1].dat);//从下往上传递信息
}
void change(ll u, ll x, ll v)//单点修改
{
if (t[u].l == t[u].r)
{
t[u].dat += v;
return;
}
ll mid = t[u].l + t[u].r >> 1;
if (x <= mid) //
change(u * 2, x, v);
else
change(u * 2 + 1, x, v);
t[u].dat = gcd(t[u * 2].dat, t[u * 2 + 1].dat);//从下往上更新信息
}
ll ask(ll u, ll l, ll r)//区间查询
{
if (l <= t[u].l && t[u].r <= r)//完全包含类型
return abs(t[u].dat);
ll mid = t[u].l + t[u].r >> 1;
ll res = 0;
if (l <= mid)
res = gcd(res, ask(u * 2, l, r)); //gcd的扩展
if (mid < r)
res = gcd(res, ask(u * 2 + 1, l, r)); //
return abs(res);
}
int main()
{
// IOS;
char ch[2];
scanf("%lld%lld", &n, &m);
for (int i = 1; i <= n; i++)
{
scanf("%lld", &a[i]);
b[i] = a[i] - a[i - 1];
}
build(1, 1, n);//切记写上调用入口
while (m--)
{
scanf("%s", ch);
scanf("%lld%lld", &l, &r);
if (ch[0] == 'C')
{
scanf("%lld", &d);
change(1, l, d);
if (r < n)
change(1, r + 1, -d);
add(l, d);
add(r + 1, -d);
}
else if (ch[0] == 'Q')
{
ll val = a[l] + getsum(l);
ll res = l < r ? ask(1ll, l + 1, r) : 0; //
printf("%lld\n", gcd(val, res));
}
}
return 0;
}
延迟标记
牛客一道小栗子A Simple Problem with Integers
上次用的树状数组做的,这次用线段树+延迟标记来做
#pragma GCC optimize(3, "Ofast", "inline")
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
typedef pair<long long, long long> pll;
const int N = 100000;
const int M = 2e6 + 1000;
const int inf = 0x3f3f3f3f;
const int mod = (1 << 31);
const int pi = acos(-1);
#define IOS \
ios::sync_with_stdio(0); \
cin.tie(0); \
cout.tie(0);
ll n, q, l, r, d;
char ch;
ll a[N];
struct segment//线段树
{
ll l, r, sum, add;
} t[N * 4];
void build(ll p, ll l, ll r)//建树
{
t[p].l = l, t[p].r = r;
if (l == r)
{
t[p].sum = a[r];
return;
}
ll mid = t[p].l + t[p].r >> 1;
build(p * 2, l, mid);
build(p * 2 + 1, mid + 1, r);
t[p].sum = t[p * 2].sum + t[p * 2 + 1].sum;
}
void spread(ll p)//延迟标记
{
if (t[p].add)
{
t[p * 2].sum += (t[p * 2].r - t[p * 2].l + 1) * t[p].add;
t[p * 2 + 1].sum += (t[p * 2 + 1].r - t[p * 2 + 1].l + 1) * t[p].add;
t[p * 2].add += t[p].add;
t[p * 2 + 1].add += t[p].add;
t[p].add = 0;
}
}
void change(ll p, ll l, ll r, ll d)//单点修改
{
if (l <= t[p].l && t[p].r <= r)
{
t[p].sum += (t[p].r - t[p].l + 1) * d;
t[p].add += d;
return;
}
spread(p); //
ll mid = t[p].l + t[p].r >> 1;
if (l <= mid)
change(p * 2, l, r, d);
if (mid < r) ///两个子树都要过一遍,所以不能是else if
change(p * 2 + 1, l, r, d);
t[p].sum = (t[p * 2].sum + t[p * 2 + 1].sum); //
}
ll ask(ll p, ll l, ll r)//区间查询
{
if (l <= t[p].l && t[p].r <= r)
return t[p].sum;
spread(p);//没有完全包含p,需要向下递归,所以延迟标记起作用了
ll mid = t[p].l + t[p].r >> 1;
ll res = 0;
if (l <= mid)
res += ask(p * 2, l, r);
if (mid < r) //两个子树都要过一遍,所以不能是else if
res += ask(p * 2 + 1, l, r);
return res;
}
int main()
{
// IOS;
scanf("%lld%lld", &n, &q);
for (int i = 1; i <= n; i++)
scanf("%lld", &a[i]);
build(1, 1, n);
while (q--)
{
scanf(" %c%lld%lld", &ch, &l, &r);//scanf()之前加空格
if (ch == 'Q')
printf("%lld\n", ask(1, l, r));
else
{
scanf("%lld", &d);
change(1, l, r, d);
}
}
return 0;
}
扫描线算法
只要找到对应结点的区间能完全覆盖当前线段区间就可以回溯统计了,并不需要更新到叶子节点,这是线段树为什么效率高的原因
线段树有一些细节:
- 要注意因为扫描线算法中线段树维护的是线段长度而不是点权,所以需要做出改变;[l,r] 维护的是xr + 1 −xl
- 因为线段树右端点的意义已经变化了,所以线段树只需要维护cnt− 1 个元素(cnt为x数组离散化后的元素个数)
- 写pushup时要注意叶子节点没有儿子需要特判,否则会数组越界
最终的答案就是线段树根节点维护的线段长度乘上当前线段与下一条线段的高度差
#pragma GCC optimize(3, "Ofast", "inline")
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
typedef pair<long long, long long> pll;
const int N = 1e5 + 10;
const int M = 2e6 + 1000;
const int inf = 0x3f3f3f3f;
const int mod = (1 << 31);
const int pi = acos(-1);
#define IOS \
ios::sync_with_stdio(0); \
cin.tie(0); \
cout.tie(0);
ll n, tot, T;
vector<double> yy;
struct segment
{
ll l, r, cnt;
double len; //
} t[N * 4 * 2];
struct line
{
double x, yl, yr;
ll f;
bool operator<(const line &t) const { return x < t.x; }
} e[N * 2];
void build(ll p, ll l, ll r)
{
t[p] = {l, r, 0, 0};
if (l == r)
return;
else //
{
ll mid = l + r >> 1;
build(p * 2, l, mid);
build(p * 2 + 1, mid + 1, r);
}
}
void pushup(ll p)
{
if (t[p].cnt)
t[p].len = (yy[t[p].r + 1] - yy[t[p].l]); //
else if (t[p].l != t[p].r)
t[p].len = t[p * 2].len + t[p * 2 + 1].len;
else //特判没有儿子的情况
t[p].len = 0;
}
void updata(ll p, ll l, ll r, ll k)
{//l,r为扫描线两端
if (l <= t[p].l && t[p].r <= r)
{
t[p].cnt += k;
pushup(p); //分情况求出结点的len
}
else //
{
ll mid = t[p].l + t[p].r >> 1;
if (l <= mid)
updata(p * 2, l, r, k);
if (mid + 1 <= r)
updata(p * 2 + 1, l, r, k); //
pushup(p);
}
}
int main()
{
// IOS;
while (scanf("%lld", &n) && n)
{
double x1, x2, y1, y2;
double res = 0.0;
yy.clear(),tot=0;//mlgb,tot记得初始化,不然会数组越界
for (int i = 1; i <= n; i++)
{
scanf("%lf%lf%lf%lf", &x1, &y1, &x2, &y2);
e[++tot] = {x1, y1, y2, 1};
yy.push_back(y1);
e[++tot] = {x2, y1, y2, -1};
yy.push_back(y2);
}
sort(e + 1, e + 1 + 2 * n);
sort(yy.begin(), yy.end());
yy.erase(unique(yy.begin(), yy.end()), yy.end());
// for (int i = 1; i <= pos; i++)
// printf("%lf\n", yy[i]);
build(1, 0, yy.size() - 2);//维护的是线段,所以建树从0~size()-1-1
for (int i = 1; i <= 2 * n; i++)//总共是2*n个x坐标,被分成了2*n-1段
{
if (i > 1)//第一个坐标不做运算
res += t[1].len * (e[i].x - e[i - 1].x);
//在离散化的坐标里面找到相等时的位置下标
ll yl = lower_bound(yy.begin(), yy.end(), e[i].yl) - yy.begin();
ll yr = lower_bound(yy.begin(), yy.end(), e[i].yr) - yy.begin();
updata(1, yl, yr - 1, e[i].f);//这里右端传入的是yr-1,不是yr
}
printf("Test case #%lld\nTotal explored area: %.2lf\n\n", ++T, res);
}
return 0;
}
牛客一道小栗子Stars in Your Window
#pragma GCC optimize(3, "Ofast", "inline")
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
typedef pair<long long, long long> pll;
const int N = 10000 + 100;
const int M = 2e6 + 1000;
const int inf = 0x3f3f3f3f;
const int mod = (1 << 31);
const int pi = acos(-1);
#define IOS \
ios::sync_with_stdio(0); \
cin.tie(0); \
cout.tie(0);
ll n, w, h, tot, res;
ll yy[N * 2]; //
struct segment
{
ll l, r, dat, lazy;
} t[N * 8];
struct edge
{
ll x, yl, yr, f;
bool operator<(const edge &t) const { return x < t.x; }
} e[N * 4];
void build(ll p, ll l, ll r)//建树
{
t[p] = {l, r, 0, 0};
if (l == r)
return;
ll mid = l + r >> 1;
build(p * 2, l, mid);
build(p * 2 + 1, mid + 1, r);
}
void spread(ll p)//延迟标记
{
if (t[p].lazy)
{
t[p * 2].dat += t[p].lazy;
t[p * 2 + 1].dat += t[p].lazy;
t[p * 2].lazy += t[p].lazy;
t[p * 2 + 1].lazy += t[p].lazy;
t[p].lazy = 0;
}
}
void updata(ll p, ll l, ll r, ll val) //l,r是未离散化后的坐标【区间更新】
{
if (l <= yy[t[p].l] && yy[t[p].r] <= r)
{
t[p].dat += val;
t[p].lazy += val; //
return;
}
spread(p);//记得要延迟标记哦
ll mid = t[p].l + t[p].r >> 1;
if (r <= yy[mid])
updata(p * 2, l, r, val);
else if (yy[mid] <= l)
updata(p * 2 + 1, l, r, val);
else
{
updata(p * 2, l, yy[mid], val);
updata(p * 2 + 1, yy[mid + 1], r, val); //
}
t[p].dat = max(t[p * 2].dat, t[p * 2 + 1].dat);
}
int main()
{
// IOS;
while (scanf("%lld%lld%lld", &n, &w, &h) == 3) //scanf()返回的是输入变量的个数
{
res = 0,tot=0;//能不能记得初始化?
ll x, y, z;
for (int i = 1; i <= n; i++)
{
scanf("%lld%lld%lld", &x, &y, &z);
e[++tot] = {x, y, y + h - 1, z}; //
yy[tot] = y;
e[++tot] = {x + w, y, y + h - 1, -z}; //
yy[tot] = y + h - 1;
}
sort(e + 1, e + 1 + tot);
sort(yy + 1, yy + 1 + tot);//离散化
ll pos = unique(yy + 1, yy + 1 + tot) - (yy + 1);//去重
build(1, 1, pos); //离散化之后的pos
for (int i = 1; i <= tot; i++)
{
updata(1, e[i].yl, e[i].yr, e[i].f);
res = max(res, t[1].dat); //取最大
}
printf("%lld\n", res);
}
return 0;
}
动态开点与线段树合并
准备阶段
struct segment
{
ll lc, rc, dat, l, r;
} t[N * 4];
动态开点
ll build()
{
++tot;
t[tot].lc = t[tot].rc = t[tot].dat = 0;
return tot;
}
单点更新升级版
void updata(ll p, ll l, ll r, ll x, ll val)
{
if (l == r)
{
t[p].dat += val;
return;
}
ll mid = l + r >> 1;
if (x <= mid)
{
if (!t[p].lc)
{
t[p].lc = build();
updata(t[p].lc, l, mid, x, val);
}
}
else if (mid + 1 <= r)
{
if (!t[p].rc)
{
t[p].rc = build();
updata(t[p].rc, mid + 1, r, x, val);
}
}
t[p].dat = max(t[t[p].lc].dat, t[t[p].rc].dat);
}
线段树合并
ll merge(ll p, ll q, ll l, ll r)
{
if (!p)
return p;
if (!q)
return q;
if (l == r)
{
t[p].dat += t[q].dat;
return p;
}
ll mid = l + r >> 1;
t[p].lc = merge(t[p].lc, t[q].lc, l, mid);
t[p].rc = merge(t[p].rc, t[q].rc, mid + 1, r);
t[p].dat = max(t[t[p].lc].dat, t[t[p].rc].dat);
return p;
}