垃圾ACMer的暑假训练220718
吉司机线段树
17.2.7 吉司机线段树
题意 ( 3 s 3\ \mathrm{s} 3 s)
给定一个长度为 n n n的序列 a a a和一个辅助序列 b b b,其中 b b b初始时与 a a a相同.
现有 m m m个操作,操作有如下五种类型:
① 1 l r k 1\ l\ r\ k 1 l r k,表示对 ∀ i ∈ [ l , r ] , a i ← a i + k \forall i\in[l,r],a_i\leftarrow a_i+k ∀i∈[l,r],ai←ai+k( k k k可为负).
② 2 l r v 2\ l\ r\ v 2 l r v,表示对 ∀ i ∈ [ l , r ] , a i ← min { a i , v } \forall i\in[l,r],a_i\leftarrow \min\{a_i,v\} ∀i∈[l,r],ai←min{ai,v}.
③ 3 l r 3\ l\ r 3 l r,表示求 ∑ i = l r a i \displaystyle\sum_{i=l}^r a_i i=l∑rai.
④ 4 l r 4\ l\ r 4 l r,表示求 max i ∈ [ l , r ] a i \displaystyle \max_{i\in[l,r]}a_i i∈[l,r]maxai.
⑤ 5 l r 5\ l\ r 5 l r,表示求 max i ∈ [ l , r ] b i \displaystyle \max_{i\in[l,r]}b_i i∈[l,r]maxbi.
每次操作后都更新 b i ← max { b i , a i } b_i\leftarrow \max\{b_i,a_i\} bi←max{bi,ai}.
第一行输入两整数 n , m ( 1 ≤ n , m ≤ 5 e 5 ) n,m\ \ (1\leq n,m\leq 5\mathrm{e}5) n,m (1≤n,m≤5e5),表示序列 a a a的长度和操作次数.第二行输入 n n n个整数 a 1 , ⋯ , a n ( − 5 e 8 ≤ a i ≤ 5 e 8 ) a_1,\cdots,a_n\ \ (-5\mathrm{e}8\leq a_i\leq 5\mathrm{e}8) a1,⋯,an (−5e8≤ai≤5e8),表示初始序列 a a a.接下来 m m m行每行输入一个操作 o p op op,输入格式如上.数据保证操作合法,且 − 2000 ≤ k ≤ 2000 , − 5 e 8 ≤ v ≤ 5 e 8 -2000\leq k\leq 2000,-5\mathrm{e}8\leq v\leq 5\mathrm{e}8 −2000≤k≤2000,−5e8≤v≤5e8.
对 o p ∈ { 3 , 4 , 5 } op\in\{3,4,5\} op∈{3,4,5}的操作,输出答案.
思路
定义:
①历史最大(小)值: a i a_i ai存放的最大(小)数.
②区间最大(小)值操作:对 ∀ i ∈ [ l , r ] , a i ← max { a i , v } \forall i\in[l,r],a_i\leftarrow \max\{a_i,v\} ∀i∈[l,r],ai←max{ai,v}( a i ← min { a i , v } a_i\leftarrow \min\{a_i,v\} ai←min{ai,v}).
③严格次大值:比最大值小的数的最大值,即最大值的前驱.
操作②为区间最小值操作,操作⑤求区间最大历史最大值.
瓶颈在于操作②后无法快速更新区间和.线段树不能直接区间取 min \min min,但可将区间内 > v >v >v的数都减去一个数,使得这些数变为 v v v.但不同的数要减去不同的数才能得到 v v v,显然不能维护一个很复杂的懒标记来表示区间内不同的数要减去的数.注意到若区间内只有一种 > v >v >v的数,即所有 > v >v >v的数都相等,则只需维护一个懒标记即可.
为使得区间内 > v >v >v的数只有一种,在线段树的节点多开三个变量:
① m a x n u m maxnum maxnum,表示区间最大值.
② c n t cnt cnt,表示区间最大值的个数.
③ p r e v prev prev,表示区间最大值的前驱,即严格次大值.
因只有在区间内只有一种数 > v >v >v时才能快速更新,则只能在满足 p r e v < v prev<v prev<v且 m a x n u m > v maxnum>v maxnum>v的节点上更新.
具体地,进行操作②的push_up操作时,有如下三种情况:
①若 m a x n u m ≤ v maxnum\leq v maxnum≤v,则当前区间最大值 ≤ v \leq v ≤v,无需更新,直接返回.
②若 p r e v < v < m a x n u m prev<v<maxnum prev<v<maxnum,则当前区间的最大值会被全部修改为 v v v,且最大值的个数不变,修改后打上懒标记返回.
③若 v ≤ p r e v v\leq prev v≤prev,则无法更新,继续递归.
线段树的节点记录的信息:
① l , r l,r l,r,表示区间的左右端点.
② s u m sum sum,表示区间和,注意开ll.
③ m a x n u m maxnum maxnum,表示区间最大值.
④ c n t cnt cnt,表示区间最大值的个数.
⑤ p r e v prev prev,表示区间最大值的前驱,即严格次大值.若区间内只有一种数,则无严格次大值,令 p r e v = − I N F prev=-INF prev=−INF.
⑥ m a x h i s maxhis maxhis,表示区间历史最大值.
⑦ l a z y _ m a x n u m lazy\_maxnum lazy_maxnum,表示区间最大值的懒标记.
⑧ l a z y _ o t h e r s lazy\_others lazy_others,表示区间非最大值的懒标记.
⑨ m a x _ l a z y _ m a x n u m max\_lazy\_maxnum max_lazy_maxnum,表示不考虑父节信息时,区间 l a z y _ m a x n u m lazy\_maxnum lazy_maxnum的最大值.
⑩ m a x _ l a z y _ o t h e r s max\_lazy\_others max_lazy_others,表示不考虑父节信息时,区间 l a z y _ o t h e r s lazy\_others lazy_others的最大值.
push_up、build、query_sum、query_maxnum、query_maxhis、modify_add、modify_min都是线段树常规操作.
为实现push_down操作,先实现一个update函数:
// add_maxnum,表示区间最大值要加的数
// add_others,表示区间非最大值要加的数
// max_add_maxnum,表示区间add_maxnum的最大值
// max_add_others,表示区间add_others的最大值
void update(int u, int add_maxnum, int add_others, int max_add_maxnum, int max_add_others) {
SegT[u].sum += (ll)add_maxnum * SegT[u].cnt + (ll)add_others * (SegT[u].r - SegT[u].l + 1 - SegT[u].cnt);
// 更新区间历史最大值、最大懒标记
SegT[u].maxhis = max(SegT[u].maxhis, SegT[u].maxnum + max_add_maxnum);
SegT[u].max_lazy_maxnum = max(SegT[u].max_lazy_maxnum, SegT[u].lazy_maxnum + max_add_maxnum);
SegT[u].max_lazy_others = max(SegT[u].max_lazy_others, SegT[u].lazy_others + max_add_others);
// 更新区间最大值、区间次大值和懒标记
SegT[u].maxnum += add_maxnum;
SegT[u].lazy_maxnum += add_maxnum;
SegT[u].lazy_others += add_others;
if (SegT[u].prev != -INF) SegT[u].prev += add_others;
}
push_down操作:
void push_down(int u) {
int curmax = max(SegT[u << 1].maxnum, SegT[u << 1 | 1].maxnum);
if (SegT[u << 1].maxnum == curmax) // 左子树
update(u << 1, SegT[u].lazy_maxnum, SegT[u].lazy_others, SegT[u].max_lazy_maxnum, SegT[u].max_lazy_others);
else
update(u << 1, SegT[u].lazy_others, SegT[u].lazy_others, SegT[u].max_lazy_others, SegT[u].max_lazy_others);
if (SegT[u << 1 | 1].maxnum == curmax) // 右子树
update(u << 1 | 1, SegT[u].lazy_maxnum, SegT[u].lazy_others, SegT[u].max_lazy_maxnum, SegT[u].max_lazy_others);
else
update(u << 1 | 1, SegT[u].lazy_others, SegT[u].lazy_others, SegT[u].max_lazy_others, SegT[u].max_lazy_others);
SegT[u].lazy_maxnum = SegT[u].lazy_others = SegT[u].max_lazy_maxnum = SegT[u].max_lazy_others = 0; // 清空懒标记
}
代码
const int MAXN = 5e5 + 5;
int n, m; // 序列长度、操作数
int a[MAXN]; // 初始序列
struct Node {
int l, r;
ll sum; // 区间和
int maxnum; // 区间最大值
int cnt; // 区间最大值的个数
int prev; // 区间最大值的前驱,即严格次大值
int maxhis; // 区间历史最大值
int lazy_maxnum; // 区间最大值的懒标记
int lazy_others; // 区间非最大值的懒标记
int max_lazy_maxnum; // 不考虑父节信息时,区间lazy_maxnum的最大值
int max_lazy_others; // 不考虑父节信息时,区间lazy_others的最大值
}SegT[MAXN << 2];
void push_up(int u) {
SegT[u].sum = SegT[u << 1].sum + SegT[u << 1 | 1].sum;
SegT[u].maxhis = max(SegT[u << 1].maxhis, SegT[u << 1 | 1].maxhis);
// 更新区间最大值、最大值的个数、严格次大值
SegT[u].maxnum = max(SegT[u << 1].maxnum, SegT[u << 1 | 1].maxnum);
if (SegT[u << 1].maxnum == SegT[u << 1 | 1].maxnum) {
SegT[u].prev = max(SegT[u << 1].prev, SegT[u << 1 | 1].prev);
SegT[u].cnt = SegT[u << 1].cnt + SegT[u << 1 | 1].cnt;
}
else if (SegT[u << 1].maxnum > SegT[u << 1 | 1].maxnum) {
SegT[u].prev = max(SegT[u << 1].prev, SegT[u << 1 | 1].maxnum);
SegT[u].cnt = SegT[u << 1].cnt;
}
else {
SegT[u].prev = max(SegT[u << 1].maxnum, SegT[u << 1 | 1].prev);
SegT[u].cnt = SegT[u << 1 | 1].cnt;
}
}
void build(int u, int l, int r) {
SegT[u].l = l, SegT[u].r = r;
if (l == r) {
SegT[u].sum = SegT[u].maxnum = SegT[u].maxhis = a[l];
SegT[u].cnt = 1;
SegT[u].prev = -INF; // 区间无严格次大值
return;
}
int mid = l + r >> 1;
build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);
push_up(u);
}
// add_maxnum,表示区间最大值要加的数
// add_others,表示区间非最大值要加的数
// max_add_maxnum,表示区间add_maxnum的最大值
// max_add_others,表示区间add_others的最大值
void update(int u, int add_maxnum, int add_others, int max_add_maxnum, int max_add_others) { // 供push_down调用
SegT[u].sum += (ll)add_maxnum * SegT[u].cnt + (ll)add_others * (SegT[u].r - SegT[u].l + 1 - SegT[u].cnt);
// 更新区间历史最大值、最大懒标记
SegT[u].maxhis = max(SegT[u].maxhis, SegT[u].maxnum + max_add_maxnum);
SegT[u].max_lazy_maxnum = max(SegT[u].max_lazy_maxnum, SegT[u].lazy_maxnum + max_add_maxnum);
SegT[u].max_lazy_others = max(SegT[u].max_lazy_others, SegT[u].lazy_others + max_add_others);
// 更新区间最大值、区间次大值和懒标记
SegT[u].maxnum += add_maxnum;
SegT[u].lazy_maxnum += add_maxnum;
SegT[u].lazy_others += add_others;
if (SegT[u].prev != -INF) SegT[u].prev += add_others;
}
void push_down(int u) {
int curmax = max(SegT[u << 1].maxnum, SegT[u << 1 | 1].maxnum);
if (SegT[u << 1].maxnum == curmax) // 左子树
update(u << 1, SegT[u].lazy_maxnum, SegT[u].lazy_others, SegT[u].max_lazy_maxnum, SegT[u].max_lazy_others);
else
update(u << 1, SegT[u].lazy_others, SegT[u].lazy_others, SegT[u].max_lazy_others, SegT[u].max_lazy_others);
if (SegT[u << 1 | 1].maxnum == curmax) // 右子树
update(u << 1 | 1, SegT[u].lazy_maxnum, SegT[u].lazy_others, SegT[u].max_lazy_maxnum, SegT[u].max_lazy_others);
else
update(u << 1 | 1, SegT[u].lazy_others, SegT[u].lazy_others, SegT[u].max_lazy_others, SegT[u].max_lazy_others);
SegT[u].lazy_maxnum = SegT[u].lazy_others = SegT[u].max_lazy_maxnum = SegT[u].max_lazy_others = 0; // 清空懒标记
}
ll query_sum(int u, int l, int r) {
if (SegT[u].l > r || SegT[u].r < l) return 0;
if (SegT[u].l >= l && SegT[u].r <= r) return SegT[u].sum;
push_down(u);
int mid = SegT[u].l + SegT[u].r >> 1;
ll res = 0;
if (l <= mid) res += query_sum(u << 1, l, r);
if (r > mid) res += query_sum(u << 1 | 1, l, r);
return res;
}
int query_maxnum(int u, int l, int r) {
if (SegT[u].l > r || SegT[u].r < l) return -INF; // 注意返回-INF
if (SegT[u].l >= l && SegT[u].r <= r) return SegT[u].maxnum;
push_down(u);
int mid = SegT[u].l + SegT[u].r >> 1;
int res = -INF;
if (l <= mid) res = max(res, query_maxnum(u << 1, l, r));
if (r > mid) res = max(res, query_maxnum(u << 1 | 1, l, r));
return res;
}
int query_maxhis(int u, int l, int r) {
if (SegT[u].l > r || SegT[u].r < l) return -INF; // 注意返回-INF
if (SegT[u].l >= l && SegT[u].r <= r) return SegT[u].maxhis;
push_down(u);
int mid = SegT[u].l + SegT[u].r >> 1;
int res = -INF;
if (l <= mid) res = max(res, query_maxhis(u << 1, l, r));
if (r > mid) res = max(res, query_maxhis(u << 1 | 1, l, r));
return res;
}
void modify_add(int u, int l, int r, int k) {
if (SegT[u].l > r || SegT[u].r < l) return;
if (SegT[u].l >= l && SegT[u].r <= r) return update(u, k, k, k, k);
push_down(u);
int mid = SegT[u].l + SegT[u].r >> 1;
if (l <= mid) modify_add(u << 1, l, r, k);
if (r > mid) modify_add(u << 1 | 1, l, r, k);
push_up(u);
}
void modify_min(int u, int l, int r, int v) {
if (SegT[u].l > r || SegT[u].r < l || v >= SegT[u].maxnum) return;
if (SegT[u].l >= l && SegT[u].r <= r && v > SegT[u].prev) // 只在区间严格次大值<v<区间最大值时更新
return update(u, v - SegT[u].maxnum, 0, v - SegT[u].maxnum, 0);
push_down(u);
int mid = SegT[u].l + SegT[u].r >> 1;
if (l <= mid) modify_min(u << 1, l, r, v);
if (r > mid) modify_min(u << 1 | 1, l, r, v);
push_up(u);
}
int main() {
cin >> n >> m;
for (int i = 1; i <= n; i++) cin >> a[i];
build(1, 1, n);
while (m--) {
int op, l, r, k, v; cin >> op >> l >> r;
switch (op) {
case 1:
cin >> k;
modify_add(1, l, r, k);
break;
case 2:
cin >> v;
modify_min(1, l, r, v);
break;
case 3:
cout << query_sum(1, l, r) << endl;
break;
case 4:
cout << query_maxnum(1, l, r) << endl;
break;
case 5:
cout << query_maxhis(1, l, r) << endl;
break;
}
}
}
计算几何
常量:
const double eps = 1e-7;
const double pi = acos(-1.0);
常用函数:
int cmp(double a, double b) { // 浮点数比较
if (fabs(a - b) < eps) return 0;
else if (a > b) return 1;
else return -1;
}
int sgn(double x) { // 符号函数
if (fabs(x) < eps) return 0;
else if (x > 0) return 1;
else return -1;
}
double x;
int fx = floor(x); // 向下取整
int cx = ceil(x); // 向上取整
int rx = round(x); // 四舍五入
if (fabs(x) < eps) cout << 0 << endl; // 防止输出-0
else cout << x <<endl;
double x = 1.000001;
// double tmpx = acos(x); // RE
if (cmp(x, -1) == 0 || cmp(x, 1) == 0) x = round(x);
double tmpx = acos(x);
浮点数计算有误差,故式子中应尽量减少三角函数、除法、开方、求幂、取对数等运算.
①减少上述运算的次数,如 1.0 / 2.0 ∗ 3.0 / 4.0 ∗ 5.0 = ( 1.0 ∗ 3.0 ∗ 5.0 ) / ( 2.0 ∗ 4.0 ) 1.0/2.0*3.0/4.0*5.0=(1.0*3.0*5.0)/(2.0*4.0) 1.0/2.0∗3.0/4.0∗5.0=(1.0∗3.0∗5.0)/(2.0∗4.0).
②在不溢出的前提下将除法转化为乘法,如 a b > c ⇔ a > b c \dfrac{a}{b}>c\Leftrightarrow a>bc ba>c⇔a>bc.
点类:
struct Point { // 点类
double x, y;
Point(double _x = 0, double _y = 0) :x(_x), y(_y) {}
bool operator==(const Point& B)const { return cmp(x, B.x) == 0 && cmp(y, B.y) == 0; }
bool operator<(const Point& B)const { return x == B.x ? x < B.y : x < B.x; } // 按x升序排列,x相同时按y升序排列
};
向量类:
typedef Point Vector; // 向量类
Vector operator+(Vector& A, Vector& B) { return Vector(A.x + B.x, A.y + B.y); }
Vector operator-(Vector& A, Vector& B) { return Vector(A.x - B.x, A.y - B.y); }
Vector operator*(Vector& A, double k) { return Vector(A.x * k, A.y * k); }
Vector operator/(Vector& A, double k) { return Vector(A.x / k, A.y / k); }
double operator*(Vector& A, Vector& B) { return A.x * B.x + A.y * B.y; } // 点乘
double operator^(Vector& A, Vector& B) { return A.x * B.y - A.y * B.x; } // 叉乘
double Dot_Product(Vector A, Vector B) { return A.x * B.x + A.y * B.y; }
double Cross_Product(Vector A, Vector B) { return A.x * B.y - A.y * B.x; }
double get_length(Vector& A) { return hypot(A.x, A.y); } // 模场
double get_angle(Vector A, Vector B) { return acos(Dot_Product(A, B) / get_length(A) / get_length(B)); } // 夹角(弧度)
double get_area(Point A, Point B, Point C) { return Cross_Product(B - A, C - A); } // 向量AB与向量AC张成的平行四边形的面积
double get_area(Vector AB, Vector AC) { return Cross_Product(AB, AC); } // 向量AB与向量AC张成的平行四边形的面积
Vector Rotate(Vector A, double rad) { return Vector(A.x * cos(rad) - A.y * sin(rad), A.x * sin(rad) + A.y * cos(rad)); } // 向量A逆时针旋转rad得到的向量
Vector Normal(Vector A) { return Vector(-A.y, A.x); } // 向量A逆时针旋转90度得到的向量
直线类、线段类:
struct Line { // 直线类
Point p; // 直线上一点
Vector v; // 方向向量
Line(Point _p, Vector _v) :p(_p), v(_v) {}
Point get_point(double t) { return p + v * t; } // 直线上参数为t的点
};
bool IsPointOnLine(Point P, Point A, Point B) { return sgn(Cross_Product(P - A, P - B)) == 0; } // 点P是否在直线AB上
bool IsPointOnSegment(Point P, Point A, Point B) { return IsPointOnLine(P, A, B) && sgn(Dot_Product(A - P, B - P)) < 0; } // 点P是否在线段AB上
double get_dis_to_line(Point P, Point A, Point B) { // 点P到直线AB的距离
Vector v1 = B - A, v2 = P - A;
return fabs(Cross_Product(v1, v2) / get_length(v1));
}
double get_dis_to_segment(Point P, Point A, Point B) { // 点P到线段AB的距离
if (A == B) return get_length(P - A);
Vector v1 = B - A, v2 = P - A, v3 = P - B;
if (sgn(Dot_Product(v1, v2)) < 0) return get_length(v2); // P的投影在线段AB外,且更靠近A
if (sgn(Dot_Product(v1, v3)) < 0) return get_length(v3); // P的投影在线段AB外,且更靠近B
return get_dis_to_line(P, A, B); // P的投影在线段AB上
}
Point get_line_intersection(Point P, Vector v, Point Q, Vector w) { // 求直线(P+vt)与(Q+vw)的交点,使用前需保证有交点
Vector u = P - Q;
double t = Cross_Product(w, u) / Cross_Product(v, w);
return P + v * t;
}
Point get_point_projection(Point P, Point A, Point B) { // 点P在直线AB上的投影
Vector v1 = B - A, v2 = P - A;
return A + v1 * (Dot_Product(v1, v2) / Dot_Product(v1, v1));
}
bool IsSegmentIntersect(Point A1, Point A2, Point B1, Point B2) { // 判断线段A1A2和B1B2是否相交
double c1 = Cross_Product(A2 - A1, B1 - A1), c2 = Cross_Product(A2 - A1, B2 - A1),
c3 = Cross_Product(B2 - B1, A2 - B1), c4 = Cross_Product(B2 - B1, A1 - B1);
return sgn(c1) * sgn(c2) <= 0 && sgn(c3) * sgn(c4) <= 0; // 若非规范相交不算相交,则去掉等号
}