递归版线段树
中心思想:
1.自上而下(最顶节点递归到叶子)
2.p,l,r三者作为整体效果
3.命令的传递,执行,反馈
构成函数:
up,build,f,dn,mdy,qry
int n; aLL sum; arr tg, a; namespace seg { #define ls(x) (x << 1) #define rs(x) (x << 1 | 1) //反馈 inline void up(int p) { sum[p] = sum[ls(p)] + sum[rs(p)]; } //对于每个节点,清空标记,截半,接收子节点效果 void build(int p, int l, int r) { tg[p] = 0; if (l == r) { sum[p] = a[l]; return; } int mid = l + r >> 1; build(ls(p), l, mid); build(rs(p), mid + 1, r); up(p); } //执行命令:tg,sum inline void f(int p, int l, int r, int k) { tg[p] += k; sum[p] += k * (r - l + 1); } //传递命令:截半,自身命令消除 inline void dn(int p, int l, int r) { int mid = l + r >> 1; f(ls(p), l, mid, tg[p]); f(rs(p), mid + 1, r, tg[p]); tg[p] = 0; } //加上子区间,截半,传递,反馈 inline void mdy(int ql, int qr, int l, int r, int p, int x) { if (ql <= l && r <= qr) { sum[p] += x * (r - l + 1); tg[p] += x; return; } dn(p, l, r); int mid = l + r >> 1; if (ql <= mid) mdy(ql, qr, l, mid, ls(p), x); if (qr > mid) mdy(ql, qr, mid + 1, r, rs(p), x); up(p); } //加上子区间,传递命令 inline ll qry(int ql, int qr, int l, int r, int p) { if (ql <= l && r <= qr) return sum[p]; ll res = 0; up(p, l, r); int mid = l + r >> 1; if (mid >= ql) res += qry(ql, qr, l, mid, ls(p)); if (mid < qr) res += qry(ql, qr, mid + 1, r, rs(p)); return res; } } // namespace seg
非递归线段树
中心思想:
1.自下而上(且每次遍历均是从最底层到最高层)
2. 用蓝色节点金字塔式包裹紫色待处理区间
3.sgt的出现(因为左右两侧需要将其裹住,所以左右两端必须空出两个空节点,sgt为大于等于n+2的最小2^k数)
由五大函数构成:
build mdy1 qry1 mdyn qryn
注意:
此方法不能处理优先级问题,如混合乘法和加法(因为自下而上的顺序的限制)
int n; aLL sum; arr tg, a; ll sgt; namespace seg { //sum的下标中,1~sgt-1都是非叶节点,sgt+1~sgt+n为叶节点,忽略sgt这个点 void build(int n) { sgt = 1; while (sgt < n + 2) sgt <<= 1; For(i, 1, n) sum[i + sgt] = a[i]; FFor(i, sgt - 1, 1) sum[i] = sum[i << 1] + sum[i << 1 | 1], tg[i] = 0; //注意这里是sgt-1不是n-1 } void mdy1(int p, ll x) { for (int s = p + sgt; s; s >>= 1) sum[s] += x; } ll qry1(int l, int r) { ll res = 0; for (int s = l + sgt - 1, t = r + sgt + 1; s ^ t ^ 1; s >>= 1, t >>= 1) //s,t都是边界相邻点,s^t^1是判断他们是不是共父节点,s>>=1,是不停向上走 { if (~s & 1) res += sum[s ^ 1]; //s^1是抓处理区域节点 if (t & 1) res += sum[t ^ 1]; } return res; } inline void mdyn(int l, int r, ll c) { int s, t, x = 1; //x是当前处理个数 ll Ln = 0, Rn = 0; //Ln,Rn分别记录左右两端共抓取多少处理区域数 for (s = l + sgt - 1, t = r + sgt + 1; s ^ t ^ 1; s >>= 1, t >>= 1, x <<= 1) { sum[s] += Ln * c; //下层传递给当前的这层 sum[t] += Rn * c; if (~s & 1) sum[s ^ 1] += x * c, tg[s ^ 1] += c, Ln += x; if (t & 1) sum[t ^ 1] += x * c, tg[t ^ 1] += c, Rn += x; } for (; s; s >>= 1, t >>= 1) //当前这层传递给上层 { sum[s] += Ln * c; sum[t] += Rn * c; } } ll qryn(int l, int r) { ll res = 0; ll Ln = 0, Rn = 0; int x = 1, s, t; for (s = l + sgt - 1, t = r + sgt + 1; s ^ t ^ 1; s >>= 1, t >>= 1, x <<= 1) { if (tg[s]) res += tg[s] * Ln; if (tg[t]) res += tg[t] * Rn; if (~s & 1) res += sum[s ^ 1], Ln += x; if (t & 1) res += sum[t ^ 1], Rn += x; } for (; s; s >>= 1, t >>= 1) { res += Ln * tg[s]; res += Rn * tg[t]; } return res; } } // namespace seg
dijkstra+线段树优化 (求最短路,效率高于堆优化)
中心思想:
1.非递归版线段树(tr存的是dis最小的实际序号)
2.dijkstra(循环n次,每次找最小的dis更新)
namespace seg { //tr存的是dis最小的实际序号 int tr[N << 2], sgt; inline void build(int n) { sgt = 1; while (sgt < n + 2) sgt <<= 1; //dis[0]=inf,dis不能memset全程,最后要留一些0,使得dis[N+1]=0 tr[0] = N+1; } inline void clr() { For(i, 1, sgt << 1) tr[i] = 0; } inline int cmp(const int &a, const int &b) { return dis[a] < dis[b] ? a : b; } //因为小所以改变,不是加法啦,把能大于w的节点都改成u inline void mdy(int u, int w) { for (int p = u + sgt; dis[tr[p]] > w; p >>= 1) tr[p] = u; dis[u] = w; } inline void del(int u) { //因为0点本身效果是dis[0]=inf,所以这里相当于就删掉了 tr[u+=sgt] = 0; for (u >>= 1; u; u >>= 1) tr[u] = cmp(tr[u << 1], tr[u << 1 | 1]); } void dij(int s, int *fi, int *Dis) { dis = Dis; // me(dis, 0x3f);不能直接这么写,反正会出问题,覆盖n+1的四倍最好 For(i, 0, 4*(n+1)) dis[i] = 2147483647; // memset(dis, 127, 4 * (n + 1)); clr(); mdy(s, 0); //循环n次,每次找离起点最近的dis,取出那个节点u,del掉(方便为下一次处理铺垫),然后对u相连所有边mdy松弛 For(i, 1, n) { int u = tr[1]; del(u); for (int v, p = fi[u]; p != -1; p = e[p].nx) if (dis[v = e[p].to] > dis[u] + e[p].w) mdy(v, e[p].w + dis[u]); } } } // namespace seg main: ce = 1; me(fi1, -1); add(u, v, w, fi1); seg::build(n); seg::dij(st, fi1, dis1);
洛谷线段树模板题(乘法和加法混合,只能用递归版)
https://www.luogu.org/problemnew/show/P3373
(2,9,10数据加强过,mmp,一直70分,干脆把int全改成ll就过了)
根据运算级优先顺序,先算乘法再算加法
namespace seg { #define ls(x) (x << 1) #define rs(x) (x << 1 | 1) aLL tr, tg1, tg2; inline void up(ll p) { tr[p] = (tr[ls(p)] + tr[rs(p)]) % mod; } void build(ll l, ll r, ll p) { tg1[p] = 0, tg2[p] = 1; if (l == r) { tr[p] = a[l]; return; } ll mid = l + r >> 1; build(l, mid, ls(p)); build(mid + 1, r, rs(p)); up(p); }
//关键!!!!tr,tg1都是先乘再加 inline void f(ll l, ll r, ll p, ll k1, ll k2) { mulmod(tr[p], k2); addmod(tr[p], k1 * (r - l + 1)); mulmod(tg2[p], k2); mulmod(tg1[p], k2); addmod(tg1[p], k1); } inline void dn(ll l, ll r, ll p) { ll mid = l + r >> 1; f(l, mid, ls(p), tg1[p], tg2[p]); f(mid + 1, r, rs(p), tg1[p], tg2[p]); tg1[p] = 0; tg2[p] = 1; } inline void add(ll ql, ll qr, ll l, ll r, ll p, ll x) { if (ql <= l && r <= qr) { addmod(tr[p], (r - l + 1) * x); addmod(tg1[p], x); return; } if (tg1[p] || tg2[p] != 1) dn(l, r, p); ll mid = l + r >> 1; if (ql <= mid) add(ql, qr, l, mid, ls(p), x); if (qr > mid) add(ql, qr, mid + 1, r, rs(p), x); up(p); } inline void mul(ll ql, ll qr, ll l, ll r, ll p, ll x) { if (ql <= l && r <= qr) { mulmod(tr[p], x); mulmod(tg2[p], x); mulmod(tg1[p], x); return; } ll mid = l + r >> 1; if (tg1[p] || tg2[p] != 1) dn(l, r, p); if (ql <= mid) mul(ql, qr, l, mid, ls(p), x); if (qr > mid) mul(ql, qr, mid + 1, r, rs(p), x); up(p); } inline ll qry(ll ql, ll qr, ll l, ll r, ll p) { if (ql <= l && r <= qr) return tr[p]%mod; ll res = 0; if (tg1[p] || tg2[p] != 1) dn(l, r, p); ll mid = l + r >> 1; if (ql <= mid) addmod(res, qry(ql, qr, l, mid, ls(p))); if (qr > mid) addmod(res, qry(ql, qr, mid + 1, r, rs(p))); return res%mod; } } // namespace seg