线段树
线段树
SP1716 GSS3 - Can you answer these queries III
SP1043 GSS1 - Can you answer these queries I
SP2713 GSS4 - Can you answer these queries IV
SP2916 GSS5 - Can you answer these queries V
SP6779 GSS7 - Can you answer these queries VII
树链剖分+线段树
注意树链剖分查询时要分左边的区间和右边的区间(因为两个区间是反的)
点击查看代码
#include <stdio.h>
#include <string.h>
#include <algorithm>
#define COV 1000000000
const int N = 100005, M = N * 2;
int n, m, w[N], now[N];
int h[N], e[M], nxt[M], idx;
int dfn[N], timestamp;
int dep[N], sz[N], top[N], fa[N], son[N];
struct Node { int sum, lmax, rmax, maxx; } tr[N * 4];
int cov[N * 4];
void add(int a, int b) {
e[++ idx] = b, nxt[idx] = h[a], h[a] = idx;
}
void dfs1(int u) {
sz[u] = 1;
for(int i = h[u]; i; i = nxt[i]) {
int v = e[i];
if(v == fa[u]) continue;
fa[v] = u, dep[v] = dep[u] + 1;
dfs1(v), sz[u] += sz[v];
if(sz[son[u]] < sz[v]) son[u] = v;
}
}
void dfs2(int u, int t) {
dfn[u] = ++ timestamp, now[timestamp] = w[u], top[u] = t;
if(!son[u]) return;
dfs2(son[u], t);
for(int i = h[u]; i; i = nxt[i]) {
int v = e[i];
if(v == fa[u] || v == son[u]) continue;
dfs2(v, v);
}
}
inline int ls(int u) { return u << 1; }
inline int rs(int u) { return u << 1 | 1; }
Node merge(const Node &l, const Node &r) {
return {
l.sum + r.sum,
std::max(l.lmax, l.sum + r.lmax),
std::max(r.rmax, r.sum + l.rmax),
std::max(std::max(l.maxx, r.maxx), l.rmax + r.lmax)
};
}
void build(int u, int l, int r) {
cov[u] = COV;
if(l == r) {
tr[u].sum = now[l];
tr[u].lmax = tr[u].rmax = tr[u].maxx = std::max(now[l], 0);
} else {
int mid = (l + r) >> 1;
build(ls(u), l, mid);
build(rs(u), mid + 1, r);
tr[u] = merge(tr[ls(u)], tr[rs(u)]);
}
}
void push_down_cov(int u, int covtag, int len) {
tr[u].sum = covtag * len;
tr[u].lmax = tr[u].rmax = tr[u].maxx = std::max(tr[u].sum, 0);
cov[u] = covtag;
}
void push_down(int u, int l, int r, int mid) {
if(cov[u] != COV) {
push_down_cov(ls(u), cov[u], mid - l + 1);
push_down_cov(rs(u), cov[u], r - mid);
cov[u] = COV;
}
}
void modify(int u, int l, int r, int x, int y, int z) {
if(x <= l && r <= y) push_down_cov(u, z, r - l + 1);
else {
int mid = (l + r) >> 1;
push_down(u, l, r, mid);
if(x <= mid) modify(ls(u), l, mid, x, y, z);
if(y > mid) modify(rs(u), mid + 1, r, x, y, z);
tr[u] = merge(tr[ls(u)], tr[rs(u)]);
}
}
Node query(int u, int l, int r, int x, int y) {
if(x <= l && r <= y) return tr[u];
else {
int mid = (l + r) >> 1;
push_down(u, l, r, mid);
if(x <= mid) {
if(y > mid) return merge(query(ls(u), l, mid, x, y), query(rs(u), mid + 1, r, x, y));
else return query(ls(u), l, mid, x, y);
} else return query(rs(u), mid + 1, r, x, y);
}
}
void modify(int x, int y, int z) {
while(top[x] != top[y]) {
if(dep[top[x]] < dep[top[y]]) std::swap(x, y);
modify(1, 1, n, dfn[top[x]], dfn[x], z);
x = fa[top[x]];
}
if(dep[x] < dep[y]) std::swap(x, y);
modify(1, 1, n, dfn[y], dfn[x], z);
}
Node query(int x, int y) {
Node res1 = {0, -COV, -COV, -COV}; // 左边的区间
Node res2 = {0, -COV, -COV, -COV}; // 右边的区间
while(top[x] != top[y]) {
if(dep[top[x]] < dep[top[y]])
res2 = merge(query(1, 1, n, dfn[top[y]], dfn[y]), res2), y = fa[top[y]];
else
res1 = merge(query(1, 1, n, dfn[top[x]], dfn[x]), res1), x = fa[top[x]];
}
if(dep[x] <= dep[y]) res2 = merge(query(1, 1, n, dfn[x], dfn[y]), res2);
else res1 = merge(query(1, 1, n, dfn[y], dfn[x]), res1);
std::swap(res1.lmax, res1.rmax); // 处理
return merge(res1, res2);
}
int main() {
scanf("%d", &n);
for(int i = 1; i <= n; i ++) scanf("%d", w + i);
for(int i = 1, a, b; i < n; i ++) scanf("%d%d", &a, &b), add(a, b), add(b, a);
dep[1] = 1, dfs1(1), dfs2(1, 1), build(1, 1, n), scanf("%d", &m);
for(int i = 1, op, a, b, c; i <= m; i ++) {
scanf("%d%d%d", &op, &a, &b);
if(op == 1) printf("%d\n", query(a, b).maxx);
else scanf("%d", &c), modify(a, b, c);
}
return 0;
}
SP1557 GSS2 - Can you answer these queries II
P4556 雨天的尾巴 /【模板】线段树合并
动态开点权值线段树+线段树合并
基本思路: 树上差分
使用动态开点线段树维护区间最大值和最大值的位置
单点修改
统计答案时累加子树和: 线段树合并
点击查看代码
#include <iostream>
#include <stdio.h>
#include <string.h>
#include <algorithm>
#include <utility>
#include <queue>
using namespace std;
const int N = 1e5 + 5, M = N << 1, logN = 20;
int n, m;
int h[N], e[M], nxt[M], idx;
int f[N][logN], dep[N];
void add(int a, int b) {
e[++ idx] = b, nxt[idx] = h[a], h[a] = idx;
}
void dfs(int u) {
for(int i = 1; i < logN; i ++)
if(f[u][i - 1]) f[u][i] = f[f[u][i - 1]][i - 1];
else break;
for(int i = h[u]; i; i = nxt[i]) {
int v = e[i];
if(v == f[u][0]) continue;
f[v][0] = u, dep[v] = dep[u] + 1;
dfs(v);
}
}
int LCA(int x, int y) {
if(dep[x] < dep[y]) swap(x, y);
for(int i = logN - 1; i >= 0; i --)
if(dep[f[x][i]] >= dep[y]) x = f[x][i];
if(x == y) return x;
for(int i = logN - 1; i >= 0; i --)
if(f[x][i] != f[y][i]) x = f[x][i], y = f[y][i];
return f[x][0];
}
struct Query {
int a, b, w;
} query[N];
int lisanhua[N], cnt;
struct Node {
int ls, rs, dat, pos; // dat 为最大值, pos 为最大值的位置
} tr[N * logN * 4];
int root[N], tot;
inline void push_up(int u) {
if(tr[tr[u].ls].dat >= tr[tr[u].rs].dat) {
tr[u].dat = tr[tr[u].ls].dat;
tr[u].pos = tr[tr[u].ls].pos;
} else {
tr[u].dat = tr[tr[u].rs].dat;
tr[u].pos = tr[tr[u].rs].pos;
}
}
void modify(int u, int l, int r, int pos, int dat) {
if(l == r) {
tr[u].dat += dat;
tr[u].pos = (tr[u].dat ? l : 0); // 需判断是否存在这个元素
} else {
int mid = (l + r) >> 1;
if(pos <= mid) {
if(!tr[u].ls) tr[u].ls = ++ tot;
modify(tr[u].ls, l, mid, pos, dat);
} else {
if(!tr[u].rs) tr[u].rs = ++ tot;
modify(tr[u].rs, mid + 1, r, pos, dat);
}
tr[u].dat = max(tr[tr[u].ls].dat, tr[tr[u].rs].dat);
tr[u].pos = tr[tr[u].ls].dat >= tr[tr[u].rs].dat ? tr[tr[u].ls].pos : tr[tr[u].rs].pos;
}
}
// 合并 p 和 q 这两棵动态开点线段树(q 到 p 上)
int merge(int p, int q, int l, int r) {
if(!p) return q;
if(!q) return p;
if(l == r) {
tr[p].dat += tr[q].dat;
tr[p].pos = (tr[p].dat ? l : 0);
return p;
}
int mid = (l + r) >> 1;
tr[p].ls = merge(tr[p].ls, tr[q].ls, l, mid);
tr[p].rs = merge(tr[p].rs, tr[q].rs, mid + 1, r);
tr[p].dat = max(tr[tr[p].ls].dat, tr[tr[p].rs].dat);
tr[p].pos = tr[tr[p].ls].dat >= tr[tr[p].rs].dat ? tr[tr[p].ls].pos : tr[tr[p].rs].pos;
push_up(p);
return p;
}
void calc(int u) {
for(int i = h[u]; i; i = nxt[i]) {
int v = e[i];
if(v == f[u][0]) continue;
calc(v);
root[u] = merge(root[u], root[v], 1, cnt); // 与子树合并
}
}
int main() {
scanf("%d%d", &n, &m);
for(int i = 1, a, b; i < n; i ++) scanf("%d%d", &a, &b), add(a, b), add(b, a);
dep[1] = 1, dfs(1);
for(int i = 1; i <= n; i ++) root[i] = ++ tot;
for(int i = 1; i <= m; i ++) {
scanf("%d%d%d", &query[i].a, &query[i].b, &query[i].w);
lisanhua[++ cnt] = query[i].w;
}
sort(lisanhua + 1, lisanhua + cnt + 1);
cnt = unique(lisanhua + 1, lisanhua + cnt + 1) - lisanhua - 1;
for(int i = 1; i <= m; i ++) {
int a = query[i].a, b = query[i].b;
int w = lower_bound(lisanhua + 1, lisanhua + cnt + 1, query[i].w) - lisanhua;
int lca = LCA(a, b);
modify(root[a], 1, cnt, w, 1);
modify(root[b], 1, cnt, w, 1);
modify(root[lca], 1, cnt, w, -1);
if(f[lca][0]) modify(root[f[lca][0]], 1, cnt, w, -1);
}
calc(1);
for(int i = 1; i <= n; i ++) printf("%d\n", lisanhua[tr[root[i]].pos]);
return 0;
}
Bzoj3306. 树 Q5.2.3.4. 树
求改点权和子树查询可以用线段树来实现
如何换根? 类似3章“树的各种东西”
其实不必真正的换根
设当前的根为 rt, 查询的节点为 x
如果 rt == x 则 直接修改整棵树即可
否则如果 x 是 rt 的祖先则 整棵树除了 (是rt的祖先的x的儿子)的子树 的点的权值都要修改(其实是两段区间)
否则 直接修改子树即可(其实是两种情况, 但是做法相同)
点击查看代码
#include <iostream>
#include <stdio.h>
#include <string.h>
#include <algorithm>
#include <utility>
#include <queue>
using namespace std;
const int N = 1e5 + 5, logN = 20;
int n, m, rt, w[N];
int h[N], e[N], nxt[N], idx;
int f[N][logN], dep[N];
int minn[N << 2];
int dfn[N], now[N], timestamp;
int sz[N];
inline void add(int a, int b) {
e[++ idx] = b, nxt[idx] = h[a], h[a] = idx;
}
void dfs(int u) {
dfn[u] = ++ timestamp, now[timestamp] = w[u], sz[u] = 1;
for(int i = 1; i < logN; i ++)
if(f[u][i - 1]) f[u][i] = f[f[u][i - 1]][i - 1];
else break;
for(int i = h[u]; i; i = nxt[i]) {
int v = e[i];
f[v][0] = u, dep[v] = dep[u] + 1;
dfs(v), sz[u] += sz[v];
}
}
int LCA(int x, int y) {
if(dep[x] < dep[y]) swap(x, y);
for(int i = logN - 1; i >= 0; i --)
if(dep[f[x][i]] >= dep[y]) x = f[x][i];
if(x == y) return x;
for(int i = logN - 1; i >= 0; i --)
if(f[x][i] != f[y][i]) x = f[x][i], y = f[y][i];
return f[x][0];
}
// 向上爬 d 个 (用于计算是rt的祖先的x的儿子)
int jump(int x, int d) {
for(int i = logN - 1; i >= 0; i --)
if(d >> i & 1) x = f[x][i];
return x;
}
inline int ls(int u) { return u << 1; }
inline int rs(int u) { return u << 1 | 1; }
void push_up(int u) {
minn[u] = min(minn[ls(u)], minn[rs(u)]);
}
void build(int u, int l, int r) {
if(l == r) minn[u] = now[l];
else {
int mid = (l + r) >> 1;
build(ls(u), l, mid);
build(rs(u), mid + 1, r);
push_up(u);
}
}
// 单点修改
void modify(int u, int l, int r, int x, int y) {
if(l == r) minn[u] = y;
else {
int mid = (l + r) >> 1;
if(x <= mid) modify(ls(u), l, mid, x, y);
else modify(rs(u), mid + 1, r, x, y);
push_up(u);
}
}
// 区间查询
int query(int u, int l, int r, int x, int y) {
if(x <= l && r <= y) return minn[u];
else {
int mid = (l + r) >> 1, res = 0x7ffffff0;
if(x <= mid) res = min(res, query(ls(u), l, mid, x, y));
if(y > mid) res = min(res, query(rs(u), mid + 1, r, x, y));
return res;
}
}
int main() {
scanf("%d%d", &n, &m);
for(int i = 1, fa; i <= n; i ++) {
scanf("%d%d", &fa, w + i);
if(fa) add(fa, i);
}
dep[1] = 1, dfs(1);
build(1, 1, n);
for(int i = 1, x, y; i <= m; i ++) {
static char op[2];
scanf("%s%d", op, &x);
if(*op == 'V') {
scanf("%d", &y);
modify(1, 1, n, dfn[x], y);
} else if(*op == 'E') {
rt = x;
} else {
if(rt == x) {
printf("%d\n", query(1, 1, n, 1, n));
} else if(LCA(x, rt) == x) {
int t = jump(rt, dep[rt] - dep[x] - 1), res = 0x3ffffff0; // t 为是rt的祖先的x的儿子
if(dfn[t] - 1 >= 1) res = min(res, query(1, 1, n, 1, dfn[t] - 1));
if(dfn[t] + sz[t] <= n) res = min(res, query(1, 1, n, dfn[t] + sz[t], n));
printf("%d\n", res);
} else {
printf("%d\n", query(1, 1, n, dfn[x], dfn[x] + sz[x] - 1));
}
}
}
return 0;
}
Bzoj3252. 攻略
dfs 序+线段树
设 w[u] 为 u 的价值
sum[u] 为 u 到根的 w 的和
线段树维护子树上的 sum 的最大值 和 最大值的位置
循环 k 次, 每次找到最大值所在的位置, 记为 p
每次将 p 的子树的 sum 都减去 w[p], 直到 p 为根节点或已经标记过的节点
然后标记节点, 向上跳
点击查看代码
#include <iostream>
#include <stdio.h>
#include <string.h>
#include <algorithm>
#include <utility>
#include <queue>
using namespace std;
typedef long long LL;
const int N = 200005;
int n, m;
int h[N], e[N << 1], nxt[N << 1], idx;
int father[N]; // 父节点
LL w[N], sum[N]; // sum[u] 为根节点到 u 的路径上的 a 的和
int dfn[N], timestamp; // dfs 序
int pos1[N], pos2[N]; // [pos1[u],pos2[u]] 对应 u 的子树
LL val[N << 2], tag[N << 2]; // 线段树, val 为 sum 的最大值
int pos[N << 2]; // max{val} 的位置
bool st[N]; // 是否玩过这个游戏
void add(int a, int b) {
e[++ idx] = b, nxt[idx] = h[a], h[a] = idx;
}
// 求 dfs 序 等
void dfs(int u, int fa) {
sum[u] = sum[fa] + w[u];
father[u] = fa;
dfn[pos1[u] = ++ timestamp] = u;
for(int i = h[u]; i; i = nxt[i])
if(e[i] != fa) dfs(e[i], u);
pos2[u] = timestamp;
}
inline int ls(int u) { return u << 1; }
inline int rs(int u) { return u << 1 | 1; }
inline void push_down(int u) {
val[ls(u)] += tag[u], tag[ls(u)] += tag[u];
val[rs(u)] += tag[u], tag[rs(u)] += tag[u];
tag[u] = 0;
}
inline void push_up(int u) {
if(val[ls(u)] > val[rs(u)]) val[u] = val[ls(u)], pos[u] = pos[ls(u)];
else val[u] = val[rs(u)], pos[u] = pos[rs(u)];
}
void build(int u, int l, int r) {
if(l == r) {
val[u] = sum[dfn[l]];
pos[u] = dfn[l];
} else {
int mid = (l + r) >> 1;
build(ls(u), l, mid);
build(rs(u), mid + 1, r);
push_up(u);
}
}
void modify(int u, int l, int r, int x, int y, LL z) {
if(x <= l && r <= y) {
val[u] += z;
tag[u] += z;
} else {
int mid = (l + r) >> 1;
push_down(u);
if(x <= mid) modify(ls(u), l, mid, x, y, z);
if(y > mid) modify(rs(u), mid + 1, r, x, y, z);
push_up(u);
}
}
int main() {
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i ++) scanf("%lld", w + i);
for(int i = 1, a, b; i < n; i ++) scanf("%d%d", &a, &b), add(a, b), add(b, a);
dfs(1, 0);
build(1, 1, n);
LL res = 0;
while(m --) {
res += val[1]; // 统计答案: 加上最大值
int p = pos[1]; // 最大值的位置
while(p) { // 迭代
if(st[p]) break;
st[p] = true;
modify(1, 1, n, pos1[p], pos2[p], -w[p]); // 子树的每个节点的权值-1
p = father[p];
}
}
printf("%lld\n", res);
return 0;
}
P3178 [HAOI2015] 树上操作
欧拉序+线段树
入栈: 加入 +dfn 出栈: 加入 -dfn
修改1: 入栈 +dfn 出栈 -dfn
修改2: 区间 +dfn*num, num为所有符号的和(入栈+1,出栈-1)
查询: 根到节点的值的和(其他子树的和抵消了)
点击查看代码
#include <iostream>
#include <stdio.h>
#include <string.h>
#include <algorithm>
#include <utility>
#include <queue>
using namespace std;
typedef long long LL;
const int N = 100005;
int n, m;
int h[N], e[N * 2], nxt[N * 2], idx;
int chushizhi[N]; // 初始值
int pos1[N], pos2[N]; // 在欧拉序上的第一次和第二次出现的位置
int dfn[N * 2], sign[N * 2], timestamp; // 欧拉序, 第一次还是第二次
int num[N * 2]; // 符号的和
LL val[N * 8], tag[N * 8]; // 值, 懒标记
void add(int a, int b) {
e[++ idx] = b, nxt[idx] = h[a], h[a] = idx;
}
// 求欧拉序...
void dfs(int u, int fa) {
dfn[++ timestamp] = u, sign[pos1[u] = timestamp] = 1;
for(int i = h[u]; i; i = nxt[i])
if(e[i] != fa) dfs(e[i], u);
dfn[++ timestamp] = u, sign[pos2[u] = timestamp] = -1;
}
inline int ls(int u) { return u << 1; }
inline int rs(int u) { return u << 1 | 1; }
// 经典下传
inline void push_down(int u, int l, int r, int mid) {
val[ls(u)] += tag[u] * (num[mid] - num[l - 1]);
tag[ls(u)] += tag[u];
val[rs(u)] += tag[u] * (num[r] - num[mid]);
tag[rs(u)] += tag[u];
tag[u] = 0;
}
// 经典上传
inline void push_up(int u) {
val[u] = val[ls(u)] + val[rs(u)];
}
void build(int u, int l, int r) {
if(l == r) val[u] = sign[l] * chushizhi[dfn[l]]; // 建树: 带上符号
else {
int mid = (l + r) >> 1;
build(ls(u), l, mid);
build(rs(u), mid + 1, r);
push_up(u);
}
}
// 区间 + sign[l]*c;
void modify(int u, int l, int r, int x, int y, int c) {
if(x <= l && r <= y) {
tag[u] += c;
val[u] += (LL)c * (num[r] - num[l - 1]); // c*所有sign的和
} else {
int mid = (l + r) >> 1;
push_down(u, l, r, mid);
if(x <= mid) modify(ls(u), l, mid, x, y, c);
if(y > mid) modify(rs(u), mid + 1, r, x, y, c);
push_up(u);
}
}
// 区间查询
LL query(int u, int l, int r, int x, int y) {
if(x <= l && r <= y) return val[u];
else {
int mid = (l + r) >> 1;
push_down(u, l, r, mid);
LL res = 0;
if(x <= mid) res += query(ls(u), l, mid, x, y);
if(y > mid) res += query(rs(u), mid + 1, r, x, y);
return res;
}
}
int main() {
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i ++) scanf("%d", chushizhi + i);
for(int i = 1, a, b; i < n; i ++) scanf("%d%d", &a, &b), add(a, b), add(b, a);
dfs(1, -1);
for(int i = 1; i <= (n << 1); i ++) num[i] = num[i - 1] + sign[i];
build(1, 1, n << 1);
for(int i = 1, op, x, y; i <= m; i ++) {
scanf("%d%d", &op, &x);
if(op == 1) {
scanf("%d", &y);
modify(1, 1, n << 1, pos1[x], pos1[x], y); // 单点修改 -> 区间修改
modify(1, 1, n << 1, pos2[x], pos2[x], y);
} else if(op == 2) {
scanf("%d", &y);
modify(1, 1, n << 1, pos1[x], pos2[x], y);
} else {
printf("%lld\n", query(1, 1, n << 1, 1, pos1[x]));
}
}
return 0;
}
P4254 [JSOI2008] Blue Mary开公司
李超线段树模板类似 P4097
求 max{p[i]*T+s[i]}
转化为李超线段树的题: s 为截距, p 为斜率
点击查看代码
#include <iostream>
#include <stdio.h>
#include <string.h>
#include <algorithm>
#include <utility>
#include <queue>
using namespace std;
const int N = 1e5 + 5;
double k[N], b[N];
const int n = 1e5;
int linecnt;
int tr[N << 2];
inline double calc(int i, int x) {
return k[i] * (x - 1) + b[i];
}
inline int ls(int u) { return u << 1; }
inline int rs(int u) { return u << 1 | 1; }
void modify(int u, int l, int r, int i) {
if(l == r) {
if(calc(tr[u], l) < calc(i, l)) tr[u] = i;
} else if(!tr[u]) tr[u] = i;
else {
int mid = (l + r) >> 1;
double y1 = calc(tr[u], mid), y2 = calc(i, mid);
if(k[tr[u]] < k[i]) {
if(y1 <= y2) modify(ls(u), l, mid, tr[u]), tr[u] = i;
else modify(rs(u), mid + 1, r, i);
} else if(k[tr[u]] > k[i]) {
if(y1 <= y2) modify(rs(u), mid + 1, r, tr[u]), tr[u] = i;
else modify(ls(u), l, mid, i);
} else {
if(b[tr[u]] < b[i]) {
tr[u] = i;
}
}
}
}
double query(int u, int l, int r, int x) {
if(l == r) return calc(tr[u], x);
else {
int mid = (l + r) >> 1;
if(x <= mid) return max(calc(tr[u], x), query(ls(u), l, mid, x));
else return max(calc(tr[u], x), query(rs(u), mid + 1, r, x));
}
}
int q;
int main() {
scanf("%d", &q);
for(int i = 1; i <= q; i ++) {
static char op[15];
scanf("%s", op);
if(*op == 'P') {
linecnt ++;
scanf("%lf%lf", b + linecnt, k + linecnt);
modify(1, 1, n, linecnt);
} else {
int x;
scanf("%d", &x);
if(linecnt == 0) puts("0");
else printf("%d\n", int(query(1, 1, n, x)) / 100);
}
}
return 0;
}
P4097 [HEOI2013] Segment
李超线段树
在线维护加入线段 y=k[i]i+b[i], x1<=i<=x2
查询对于任意 i max{k[i]i+b[i]} 及 min{k[i]*i+b[i]}
线段树的每个节点维护的线段下标
定义域 包含 这段区间 的线段 构成的集合记为 S
设 折线 l 为 S 中 所有线段 在 这段区间上的 取值的最大值 构成的折线(类似上轮廓)
这个节点存的是 S 中 所有线段与 l 有最大交集的直线(不是指长度, 是指横坐标之差) 的下标
在插入时用 mid 的 y 值 和 斜率 比较, 判断向哪边递归
点击查看代码
#include <iostream>
#include <stdio.h>
#include <string.h>
#include <algorithm>
#include <utility>
#include <queue>
using namespace std;
const int N = 1e5 + 50;
double k[N], b[N]; // 线
const int m = 4e4; // 总数
int n; // 编号数
int tr[N << 2]; // 最值所在的下标
inline int ls(int u) { return u << 1; }
inline int rs(int u) { return u << 1 | 1; }
// line[idx] 在 x 的取值
inline double calc(int i, int x) {
return k[i] * x + b[i];
}
// 加入线段 line[i], 从 x1 到 x2
void insert(int u, int l, int r, int x1, int x2, int i) {
int mid = (l + r) >> 1;
if(x1 <= l && r <= x2) { // 需要更新
if(l == r) {
if(calc(tr[u], l) < calc(i, l)) tr[u] = i;
} else if(!tr[u]) tr[u] = i;
else {
double y1 = calc(tr[u], mid), y2 = calc(i, mid);
if(k[tr[u]] < k[i]) {
if(y1 <= y2) insert(ls(u), l, mid, x1, x2, tr[u]), tr[u] = i;
else insert(rs(u), mid + 1, r, x1, x2, i);
} else if(k[tr[u]] > k[i]) {
if(y1 <= y2) insert(rs(u), mid + 1, r, x1, x2, tr[u]), tr[u] = i;
else insert(ls(u), l, mid, x1, x2, i);
} else { // 斜率相等
if(b[tr[u]] < b[i]) tr[u] = i; // i 的纵坐标更大
}
}
} else {
if(x1 <= mid) insert(ls(u), l, mid, x1, x2, i);
if(x2 > mid) insert(rs(u), mid + 1, r, x1, x2, i);
}
}
// 单点查询:
int query(int u, int l, int r, int x) {
if(l == r) return /* cout << "RET = " << tr[u] << endl, */ tr[u];
else {
int mid = (l + r) >> 1, res = -1;
if(x <= mid) res = query(ls(u), l, mid, x); // 此时左区间的值
else res = query(rs(u), mid + 1, r, x); // 此时右区间的值
// cout << "res = " << res << endl;
// cout << "ret = " << (calc(res, x) > calc(tr[u], x) ? res : tr[u]) << endl;
return calc(res, x) > calc(tr[u], x) ? res : tr[u]; // 谁更大
}
}
int q, ans;
int main() {
scanf("%d", &q);
for(int i = 1, op; i <= q; i ++) {
scanf("%d", &op);
if(op) {
int x1, y1, x2, y2;
scanf("%d%d%d%d", &x1, &y1, &x2, &y2);
x1 = (x1 + ans - 1) % 39989 + 1, y1 = (y1 + ans - 1) % 1000000000 + 1;
x2 = (x2 + ans - 1) % 39989 + 1, y2 = (y2 + ans - 1) % 1000000000 + 1;
if(x1 > x2) swap(x1, x2), swap(y1, y2);
// cout << "::: " << x1 << ' ' << y1 << ' ' << x2 << ' ' << y2 << endl;
n ++;
if(x1 == x2) {
k[n] = 0;
b[n] = max(y1, y2);
} else {
k[n] = (double)(y2 - y1) / (x2 - x1);
b[n] = y1 - k[n] * x1;
}
insert(1, 1, m, x1, x2, n);
} else {
int x;
scanf("%d", &x);
x = (x + ans - 1) % 39989 + 1;
printf("%d\n", ans = query(1, 1, m, x));
}
// for(int j = 1; j <= m * 4; j ++) {
// cout << tr[j] << ' ';
// }
// cout << endl;
}
return 0;
}
Bzoj1171. 大sz的游戏
线段树套队列优化DP
f[i] 表示 1 到 i 的最短时间
f[i] = min{f[j]} + 1
其中 j 满足:
- [xi, yi] ∩ [xj, yj] = {}
- li - lj <= L
- 0 < j < i
条件 2, 3 可用双指针维护
条件 1 : 线段树套队列(单调队列)
每个点对应的区间维护一个维护 f 的单调队列
使用标记永久化
点击查看代码
#include <iostream>
#include <stdio.h>
#include <string.h>
#include <algorithm>
#include <utility>
#include <queue>
#include <list>
using namespace std;
const int N = 2.5e5, inf = 2e9;
int n, f[N];
list<int> tr[N << 3];
int res[N << 3];
inline int ls(int u) { return u << 1; }
inline int rs(int u) { return u << 1 | 1; }
void modify(int u, int l, int r, int x, int y, int idx, int type) {
if(x <= l && r <= y) {
if(type) { // 向单调队列中加入 idx
while(tr[u].size() && f[tr[u].back()] >= f[idx]) tr[u].pop_back(); // 弹掉不符合条件的
tr[u].push_back(idx); // 加入
} else { // 这些 tr[u].front() 是 <= idx 的 => 删掉
while(tr[u].size() && tr[u].front() <= idx) tr[u].pop_front();
}
} else {
int mid = (l + r) >> 1;
if(x <= mid) modify(ls(u), l, mid, x, y, idx, type);
if(y > mid) modify(rs(u), mid + 1, r, x, y, idx, type);
}
res[u] = (l < r ? min(res[ls(u)], res[rs(u)]) : inf); // 用左右儿子更新
if(tr[u].size()) res[u] = min(res[u], f[tr[u].front()]); // 用单调队列的队头更新
}
int query(int u, int l, int r, int x, int y) {
if(x <= l && r <= y) return res[u];
else {
int mid = (l + r) >> 1, ans = inf;
if(x <= mid) ans = min(ans, query(ls(u), l, mid, x, y));
if(y > mid) ans = min(ans, query(rs(u), mid + 1, r, x, y));
if(tr[u].size()) ans = min(ans, f[tr[u].front()]);
return ans;
}
}
void build(int u, int l, int r) {
res[u] = inf;
if(l != r) {
int mid = (l + r) >> 1;
build(ls(u), l, mid);
build(rs(u), mid + 1, r);
}
}
int L;
int x[N], y[N], l[N];
int lisanhua[N << 1], tot;
int main() {
scanf("%d%d", &n, &L);
for(int i = 2; i <= n; i ++) {
scanf("%d%d%d", x + i, y + i, l + i);
lisanhua[++ tot] = x[i];
lisanhua[++ tot] = y[i];
}
sort(lisanhua + 1, lisanhua + tot + 1);
tot = unique(lisanhua + 1, lisanhua + tot + 1) - lisanhua - 1;
for(int i = 2; i <= n; i ++)
x[i] = lower_bound(lisanhua + 1, lisanhua + tot + 1, x[i]) - lisanhua,
y[i] = lower_bound(lisanhua + 1, lisanhua + tot + 1, y[i]) - lisanhua;
x[1] = 1, y[1] = tot, f[0] = 0;
build(1, 1, tot);
for(int i = 1, j = 1; i <= n; i ++) {
while(j < i && l[i] - l[j] > L) {
if(x[j] <= y[j])
modify(1, 1, tot, x[j], y[j], j, 0);
j ++;
}
if(i > 1) f[i] = query(1, 1, tot, x[i], y[i]) + 1;
if(x[i] <= y[i])
modify(1, 1, tot, x[i], y[i], i, 1);
}
for(int i = 2; i <= n; i ++)
if(f[i] >= inf) puts("-1");
else printf("%d\n", f[i]);
return 0;
}
P3437 [POI2006]TET-Tetris 3D
点击查看代码
#include <iostream>
#include <stdio.h>
#include <string.h>
#include <algorithm>
#include <utility>
#include <queue>
#include <limits.h>
#include <stdint.h>
using namespace std;
const int N = 1001;
inline int ls(int u) { return u << 1; }
inline int rs(int u) { return u << 1 | 1; }
int n, m, q;
// 维护 y 的线段树
struct segment_tree {
int maxx[N << 2], cov[N << 2];
void modify(int u, int l, int r, int x, int y, int val) {
maxx[u] = max(maxx[u], val);
if(x <= l && r <= y) cov[u] = max(cov[u], val);
else {
int mid = (l + r) >> 1;
if(x <= mid) modify(ls(u), l, mid, x, y, val);
if(y > mid) modify(rs(u), mid + 1, r, x, y, val);
}
}
int query(int u, int l, int r, int x, int y) {
int res = cov[u];
if(x <= l && r <= y) return max(res, maxx[u]);
else {
int mid = (l + r) >> 1;
if(x <= mid) res = max(res, query(ls(u), l, mid, x, y));
if(y > mid) res = max(res, query(rs(u), mid + 1, r, x, y));
return res;
}
}
} maxx[N << 2], cov[N << 2];
// SGT2的根节点 SGT2的修改区间 x的范围 y的范围
void modify(int u, int l, int r, int x, int y, int L, int R, int val) {
maxx[u].modify(1, 1, m, L, R, val);
if(x <= l && r <= y) cov[u].modify(1, 1, m, L, R, val);
else {
int mid = (l + r) >> 1;
if(x <= mid) modify(ls(u), l, mid, x, y, L, R, val);
if(y > mid) modify(rs(u), mid + 1, r, x, y, L, R, val);
}
}
int query(int u, int l, int r, int x, int y, int L, int R) {
int res = cov[u].query(1, 1, m, L, R);
if(x <= l && r <= y) return max(res, maxx[u].query(1, 1, m, L, R));
else {
int mid = (l + r) >> 1;
if(x <= mid) res = max(res, query(ls(u), l, mid, x, y, L, R));
if(y > mid) res = max(res, query(rs(u), mid + 1, r, x, y, L, R));
return res;
}
}
int main() {
scanf("%d%d%d", &n, &m, &q);
for(int i = 1, x, y, z, xx, yy; i <= q; i ++) {
scanf("%d%d%d%d%d", &x, &y, &z, &xx, &yy);
int res = query(1, 1, n, xx + 1, xx + x, yy + 1, yy + y);
modify(1, 1, n, xx + 1, xx + x, yy + 1, yy + y, res + z);
}
printf("%d\n", query(1, 1, n, 1, n, 1, m));
return 0;
}
P4198 楼房重建
奇葩线段树
点击查看代码
#include <iostream>
#include <stdio.h>
#include <string.h>
#include <algorithm>
#include <utility>
#include <array>
#include <queue>
using namespace std;
const int N = 1e5 + 5;
int n, m;
int len[N * 4]; // 区间上可以看到的建筑的个数, len[1] 即为答案
double maxx[N * 4]; // 区间上最大斜率
inline int ls(int u) { return u << 1; }
inline int rs(int u) { return u << 1 | 1; }
// u(即[l,r]) 里边有多少个斜率 > h 的
int find(int u, double h, int l, int r) {
if(maxx[u] <= h) return 0; // 没有斜率 > h 的
if(l == r) return 1; // 斜率 > h 且只有一个点
int mid = (l + r) >> 1;
if(h >= maxx[ls(u)]) return find(rs(u), h, mid + 1, r); // 只有右边有
else return find(ls(u), h, l, mid) + (len[u] - len[ls(u)]); // 左边能看到的 + 看得到但左边看不到
}
inline void push_up(int u, int l, int r) {
int mid = (l + r) >> 1;
maxx[u] = max(maxx[ls(u)], maxx[rs(u)]); // 更新最大斜率
len[u] = len[ls(u)] + find(rs(u), maxx[ls(u)], mid + 1, r); // 左边能看到的 + 比左边斜率最大还大的个数
}
void modify(int u, int l, int r, int x, double y) {
if(l == x && r == x) maxx[u] = y, len[u] = 1;
else {
int mid = (l + r) >> 1;
if(x <= mid) modify(ls(u), l, mid, x, y);
else modify(rs(u), mid + 1, r, x, y);
push_up(u, l, r);
}
}
int main() {
scanf("%d%d", &n, &m);
for(int i = 1, x, y; i <= m; i ++) {
scanf("%d%d", &x, &y);
modify(1, 1, n, x, (double)y / x);
printf("%d\n", len[1]);
}
return 0;
}
P4314 CPU 监控
CPU 监控
push_down 有门道
题意: 有 m 个操作:
Q X Y:询问从 X 到 Y 这段时间内 CPU 最高使用率。
A X Y:询问从 X 到 Y 这段时间内之前列出的事件使 CPU 达到过的最高使用率。
P X Y Z:列出一个事件这个事件使得从 X 到 Y 这段时间内 CPU 使用率增加 Z。
C X Y Z:列出一个事件这个事件使得从 X 到 Y 这段时间内 CPU 使用率变为 Z。
其中 X,Y∈[1..n]
**做法:
每个节点维护
max 最大值, hismax 历史最大值
add 加法标记, hisadd 历史最大加法标记
cov 覆盖标记, hiscov 历史最大覆盖标记, 为 INT_MIN 是没有被覆盖
标记先看 cov 再看 add
更新时讨论是否被覆盖, 注意跟新历史最大标记
点击查看代码
#include <iostream>
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <limits.h>
#include <algorithm>
#include <utility>
#include <queue>
const int N = 1e5 + 5;
int n, m;
int chushizhi[N];
struct Node {
int max, hismax; // 最大值, 历史最大值
int add, hisadd; // 加法标记, 历史最大加法标记
int cov, hiscov; // 覆盖标记, 历史最大覆盖标记, 为 INT_MIN 是没有被覆盖
} tr[N * 4];
inline int ls(int u) { return u << 1; }
inline int rs(int u) { return u << 1 | 1; }
// 处理 add 的 push_down
inline void push_down_add(int add, int hisadd, int s) {
if(tr[s].cov == INT_MIN) { // 没有被覆盖
tr[s].hisadd = std::max(tr[s].hisadd, hisadd + tr[s].add);
tr[s].hismax = std::max(tr[s].hismax, hisadd + tr[s].max);
tr[s].add += add;
tr[s].max += add;
} else {
tr[s].hiscov = std::max(tr[s].hiscov, hisadd + tr[s].cov);
tr[s].hismax = std::max(tr[s].hismax, hisadd + tr[s].max);
tr[s].cov += add;
tr[s].max += add;
}
}
// 处理 cov 的 push_down
inline void push_down_cov(int cov, int hiscov, int s) {
tr[s].cov = tr[s].max = cov;
tr[s].hiscov = std::max(tr[s].hiscov, hiscov);
tr[s].hismax = std::max(tr[s].hismax, hiscov);
}
inline void push_down(int u) {
push_down_add(tr[u].add, tr[u].hisadd, ls(u));
push_down_add(tr[u].add, tr[u].hisadd, rs(u));
tr[u].add = tr[u].hisadd = 0;
if(tr[u].cov != INT_MIN) { // 覆盖了
push_down_cov(tr[u].cov, tr[u].hiscov, ls(u));
push_down_cov(tr[u].cov, tr[u].hiscov, rs(u));
tr[u].cov = tr[u].hiscov = INT_MIN;
}
}
inline void push_up(int u) {
tr[u].max = std::max(tr[ls(u)].max, tr[rs(u)].max);
tr[u].hismax = std::max(tr[u].hismax, tr[u].max);
}
using namespace std;
void build(int u, int l, int r) {
tr[u].cov = tr[u].hiscov = INT_MIN;
tr[u].add = tr[u].hisadd = 0;
if(l == r) {
tr[u].max = tr[u].hismax = chushizhi[l];
} else {
int mid = (l + r) >> 1;
build(ls(u), l, mid);
build(rs(u), mid + 1, r);
push_up(u);
}
}
// P 操作
void modify1(int u, int l, int r, int x, int y, int z) {
if(x <= l && r <= y) {
push_down_add(z, max(z, 0), u);
} else {
int mid = (l + r) >> 1;
push_down(u);
if(x <= mid) modify1(ls(u), l, mid, x, y, z);
if(y > mid) modify1(rs(u), mid + 1, r, x, y, z);
push_up(u);
}
}
// C 操作
void modify2(int u, int l, int r, int x, int y, int z) {
if(x <= l && r <= y) {
push_down_cov(z, z, u);
} else {
int mid = (l + r) >> 1;
push_down(u);
if(x <= mid) modify2(ls(u), l, mid, x, y, z);
if(y > mid) modify2(rs(u), mid + 1, r, x, y, z);
push_up(u);
}
}
// Q 询问
int query1(int u, int l, int r, int x, int y) {
if(x <= l && r <= y) {
return tr[u].max;
} else {
int mid = (l + r) >> 1, res = INT_MIN;
push_down(u);
if(x <= mid) res = max(res, query1(ls(u), l, mid, x, y));
if(y > mid) res = max(res, query1(rs(u), mid + 1, r, x, y));
return res;
}
}
// A 询问
int query2(int u, int l, int r, int x, int y) {
if(x <= l && r <= y) {
return tr[u].hismax;
} else {
int mid = (l + r) >> 1, res = INT_MIN;
push_down(u);
if(x <= mid) res = max(res, query2(ls(u), l, mid, x, y));
if(y > mid) res = max(res, query2(rs(u), mid + 1, r, x, y));
return res;
}
}
int main() {
scanf("%d", &n);
for(int i = 1; i <= n; i ++) scanf("%d", chushizhi + i);
build(1, 1, n);
scanf("%d", &m);
for(int i = 1, x, y, z; i <= m; i ++) {
static char op[3];
scanf("%s%d%d", op, &x, &y);
if(*op == 'Q') {
printf("%d\n", query1(1, 1, n, x, y));
} else if(*op == 'A') {
printf("%d\n", query2(1, 1, n, x, y));
} else if(*op == 'P') {
scanf("%d", &z);
modify1(1, 1, n, x, y, z);
} else {
scanf("%d", &z);
modify2(1, 1, n, x, y, z);
}
}
return 0;
}
P4145 上帝造题的七分钟 2 / 花神游历各国
1e12 6 次开平方后为 1
将区间求改退化为单点修改
若一个区间的数均为 1 (即 s[u]==r-l+1) 则不进行修改
点击查看代码
#include <iostream>
#include <stdio.h>
#include <string.h>
#include <algorithm>
#include <utility>
#include <array>
#include <queue>
#include <math.h>
using namespace std;
typedef long long LL;
const int N = 1e5 + 5;
int n, m;
LL a[N];
LL tr[N * 4];
inline int ls(int u) { return u << 1; }
inline int rs(int u) { return u << 1 | 1; }
inline void push_up(int u) {
tr[u] = tr[ls(u)] + tr[rs(u)];
}
void build(int u, int l, int r) {
if(l == r) tr[u] = a[l];
else {
int mid = (l + r) >> 1;
build(ls(u), l, mid);
build(rs(u), mid + 1, r);
push_up(u);
}
}
void modify(int u, int l, int r, int x, int y) {
if(l == r) tr[u] = sqrt(tr[u]);
else if(x <= l && r <= y && tr[u] == r - l + 1);
else {
int mid = (l + r) >> 1;
if(x <= mid) modify(ls(u), l, mid, x, y);
if(y > mid) modify(rs(u), mid + 1, r, x, y);
push_up(u);
}
}
LL query(int u, int l, int r, int x, int y) {
if(x <= l && r <= y) return tr[u];
else {
int mid = (l + r) >> 1;
LL res = 0;
if(x <= mid) res += query(ls(u), l, mid, x, y);
if(y > mid) res += query(rs(u), mid + 1, r, x, y);
return res;
}
}
int main() {
scanf("%d", &n);
for(int i = 1; i <= n; i ++) scanf("%lld", a + i);
build(1, 1, n);
scanf("%d", &m);
for(int i = 1, op, l, r; i <= m; i ++) {
scanf("%d%d%d", &op, &l, &r);
if(l > r) swap(l, r);
if(op == 0) {
modify(1, 1, n, l, r);
} else {
printf("%lld\n", query(1, 1, n, l, r));
}
}
return 0;
}
P2048 [NOI2010] 超级钢琴
题意: 给定[L,R], 求出长度在[L,R]中的 k 个区间的权值和的最大值
先用前缀和sum, 固定起点i, 求sum[j]-sum[i-1]的最大值, 其中j∈[i+l-1,i+r-1], 可以使用ST表
用一个堆, 状态为 (i,l,r) 表示终点在[l,r]中, 起点为i的区间的和的最大值, 初始时将原序列的这些最大值状态加入队中
每次从堆中取出最大值 (i,l,r), 从st表中可以得到最大值所在的位置m, 将这个区间分裂为(i,l,m-1)和(i,m+1,r)加入队列即可
这样相当于把终点在[l,r]的最优方案去点(新的区间中没有m了)
点击查看代码
#include <iostream>
#include <stdio.h>
#include <string.h>
#include <algorithm>
#include <utility>
#include <queue>
#include <assert.h>
using namespace std;
const int N = 500005, logN = 25;
int st[N][logN]; // 区间最大值
int pos[N][logN];// 最大值的位置
int lg2[N]; // log
int n, k, L, R;
int query(int l, int r) { // ST表返回最大值的下标
int t = lg2[r - l + 1];
if(st[l][t] >= st[r - (1 << t) + 1][t]) return pos[l][t];
else return pos[r - (1 << t) + 1][t];
};
struct foo {
int maxx; // 区间和最大值
int i, l, r; // 状态 (i,l,r) 表示起点为 i, 终点在[l,r]之间
bool operator < (const foo &x) const {
return maxx < x.maxx;
}
};
priority_queue<foo> heap;
int main() {
scanf("%d%d%d%d", &n, &k, &L, &R);
lg2[0] = -1;
for(int i = 1; i <= n; i ++) lg2[i] = lg2[i >> 1] + 1;
for(int i = 1, x; i <= n; i ++) {
scanf("%d", &x);
st[i][0] = st[i - 1][0] + x; // 前缀和
pos[i][0] = i;
}
for(int j = 1; j < logN; j ++)
for(int i = 1; i + (1 << j) - 1 <= n; i ++)
if(st[i][j - 1] >= st[i + (1 << (j - 1))][j - 1]) st[i][j] = st[i][j - 1], pos[i][j] = pos[i][j - 1];
else st[i][j] = st[i + (1 << (j - 1))][j - 1], pos[i][j] = pos[i + (1 << (j - 1))][j - 1];
for(int i = 1, l = L, r = R; l <= n; i ++, l ++, r ++) // 初始条件
heap.push({st[query(l, min(n, r))][0] - st[i - 1][0], i, l, min(n, r)});
long long sum = 0;
while(k -- && heap.size()) {
auto [maxx, i, l, r] = heap.top();
heap.pop(), sum += maxx;
int m = query(l, r);
if(m - 1 >= l) heap.push({st[query(l, m - 1)][0] - st[i - 1][0], i, l, m - 1});
if(m + 1 <= r) heap.push({st[query(m + 1, r)][0] - st[i - 1][0], i, m + 1, r});
}
printf("%lld\n", sum);
return 0;
}
P2475 [SCOI2008]斜堆
结论题(啊啊啊)
记号:
1=这个节点是由根节点一直向左走得到的(极左)
2=这个节点没有右子树
结论:
- 非叶子节点必有左子树
1: 每一个节点在被插入时一定满足#1和#2(由递归定义)
其余时候在左右儿子被交换后左子树一定非空 - 最后插入的节点一定是(满足#1和#2的深度最小的节点)或(满足#1和#2的深度最小的节点的左儿子是叶节点时的左儿子)
2: 如果最后插入的节点不是叶节点且插入后它的某个祖先满足#1和#2
因为最后插入的节点不是叶节点, 所以插入这个节点时所有经过的节点的左儿子不为空
则满足#1和#2的祖先在插入后右子树为空->插入前左子树为空(由上行知这个祖先在插入新节点的过程中交换过左右子树)
则这个祖先之前不满足结论1,矛盾! - 为了保证字典序最小, 如果满足#1和#2的深度最小的节点的左儿子是叶节点, 则选择这个节点的左儿子, 否则选择这个节点
点击查看代码
#include <iostream>
#include <stdio.h>
#include <string.h>
#include <algorithm>
#include <utility>
#include <queue>
using namespace std;
const int N = 105;
int n;
int l[N], r[N], fa[N];
int ans[N], rt;
int main() {
scanf("%d", &n);
for(int i = 0; i <= n; i ++) l[i] = r[i] = -1;
for(int i = 1, x; i <= n; i ++) {
scanf("%d", &x);
if(x < 100) l[x] = i, fa[i] = x;
else r[x - 100] = i, fa[i] = x - 100;
}
for(int i = 0; i <= n; i ++) {
int now = rt; // 找到要删除的节点: 从根节点一直向左走, 只需判断是否满足条件2即可
while(r[now] != -1) now = l[now]; // 若当前不满足条件2则向左移动
if(l[now] != -1 && l[l[now]] == -1 && r[l[now]] == -1) now = l[now]; // 左儿子为叶节点且更大->选左儿子
ans[i] = now; // 记录答案
if(now == rt) rt = l[rt]; // 是根节点->直接删除
else {
l[fa[now]] = l[now]; // 删除这个节点
if(l[now] != -1) fa[l[now]] = fa[now]; // 注意更新父节点
while(now != rt) now = fa[now], swap(l[now], r[now]); // “撤销”: 跟插入反着来
}
}
for(int i = n; i >= 0; i --) printf("%d ", ans[i]);
return 0;
}
P2685 [TJOI2012]桥
题意: 给定一个无向图, 求删除一条边后1到n最短路的最大值 和方案数
先求出 dist1[u]=1到u的最短路的长度, dist2[u]=u到n的最短路的长度(由于这是无向图,所以u到n的最短路=n到u的最短路)
只考虑删除1~n的最短路上的边:
对于每一条不在1~n的最短路上的边(u,v,w), 如果要选这条边, 则经过这条边的最短路的长度为dist1[u]+w+dist2[v]
这条边对应的最短路可以用来更新1~n的最短路上的一些边
是什么边?如果某条边在这条最短路与1~n的最短路的交集上, 则这个边不能被更新
设pos1[u]表示1u的最短路与1n的最短路上的最后一个交点(在1~n的最短路上的第几个点)
设pos2[u]表示un的最短路与1n的最短路上的最前一个交点(在1~n的最短路上的第几个点)
则可以用这个最短路跟新的边在1~n的最短路上的点[pos1[u],pos2[v]]之间
将这些边从1开始编号,则这些边在标号为[pos1[u],pos2[v]-1]的边(1~n最短路上的第_条边)之间
每次更新就是用线段树维护1n路径上的最小值,最后1n最短路上每条边的答案就是单点查询的结果
点击查看代码
#include <stdio.h>
#include <string.h>
#include <algorithm>
#include <queue>
const int N = 100005, M = 400005;
int n, m;
int h[N], e[M], w[M], nxt[M], idx;
int dist1[N], dist2[N], fa[N], to_fa[N];
bool st[N];
std::vector<int> path; // 1~n 最短路径
int tot; // 最短路的边数
int id[N]; // 如果在 1 到 n 的最短路上, 则为 1~n 的最短路上的第几个点
int pos1[N]; // 1~u 的最短路与 1~n 的最短路最后重合的点
int pos2[N]; // u~n 的最短路与 n~1 的最短路最后重合的点
int q[N];
int tr[N << 2], tag[N << 2];
int ans[N];
bool in[M]; // 是否在最短路中
void add(int a, int b, int c) {
e[++ idx] = b, w[idx] = c, nxt[idx] = h[a], h[a] = idx;
}
void Dijkstra(int start, int *dist) {
memset(dist + 1, 0x3f, sizeof(int) * n);
memset(st + 1, false, sizeof(bool) * n);
typedef std::pair<int, int> PII;
std::priority_queue<PII, std::vector<PII>, std::greater<PII> > heap;
heap.push({dist[start] = 0, start});
while(heap.size()) {
int u = heap.top().second;
heap.pop();
if(st[u]) continue;
st[u] = false;
for(int i = h[u]; i; i = nxt[i]) {
int v = e[i];
if(dist[v] > dist[u] + w[i]) {
dist[v] = dist[u] + w[i];
fa[v] = u, to_fa[v] = i;
heap.push({dist[v], v});
}
}
}
}
void bfs(int start, int *dist, int *pos) {
int hh = 0, tt = 0;
q[tt ++] = start;
pos[start] = id[start];
while(hh != tt) {
int u = q[hh ++];
for(int i = h[u]; i; i = nxt[i]) {
int v = e[i];
if(!pos[v] && !id[v] && dist[u] + w[i] == dist[v]) {
pos[v] = id[start];
q[tt ++] = v;
}
}
}
}
inline int ls(int u) { return u << 1; }
inline int rs(int u) { return u << 1 | 1; }
void modify(int u, int l, int r, int x, int y, int c) {
tr[u] = std::min(tr[u], c);
if(x <= l && r <= y) tag[u] = std::min(tag[u], c);
else {
int mid = (l + r) >> 1;
if(x <= mid) modify(ls(u), l, mid, x, y, c);
if(y > mid) modify(rs(u), mid + 1, r, x, y, c);
}
}
int query(int u, int l, int r, int x, int y) {
int res = tag[u];
if(x <= l && r <= y) return std::min(res, tr[u]);
else {
int mid = (l + r) >> 1;
if(x <= mid) res = std::min(res, query(ls(u), l, mid, x, y));
if(y > mid) res = std::min(res, query(rs(u), mid + 1, r, x, y));
return res;
}
}
int main() {
scanf("%d%d", &n, &m);
for(int i = 1, a, b, c; i <= m; i ++)
scanf("%d%d%d", &a, &b, &c), add(a, b, c), add(b, a, c);
Dijkstra(n, dist2), Dijkstra(1, dist1), fa[1] = 0;{
int u = n;
while(u) path.push_back(u), in[to_fa[u]] = true, u = fa[u];
std::reverse(path.begin(), path.end());
for(int i = 0; i < int(path.size()); i ++) id[path[i]] = i + 1;}
for(int u : path) bfs(u, dist1, pos1);
std::reverse(path.begin(), path.end());
for(int u : path) bfs(u, dist2, pos2);
std::reverse(path.begin(), path.end());
tot = path.size() - 1U;
for(int i = 1; i <= (tot << 2); i ++) tr[i] = tag[i] = 0x3f3f3f3f;
for(int u = 1; u <= n; u ++)
for(int i = h[u]; i; i = nxt[i]) {
int v = e[i];
if(!in[i] && pos1[u] < pos2[v])
modify(1, 1, tot, pos1[u], pos2[v] - 1, dist1[u] + w[i] + dist2[v]);
}
for(int i = 1; i <= tot; i ++) ans[i] = query(1, 1, tot, i, i);
int res = *std::max_element(ans + 1, ans + tot + 1), cnt = 0;
for(int i = 1; i <= tot; i ++)
if(ans[i] == res) cnt ++;
printf("%d %d\n", res, (res == dist1[n] ? m : cnt));
return 0;
}
P3582 [POI2015] KIN (我的电影)
点击查看代码
// val 第一次出现则为 +val
// val 第二次出现则为 -val
// val 第三次出现则为 0
// 线段树维护连续区间最大值
#include <iostream>
#include <stdio.h>
#include <string.h>
#include <algorithm>
#include <utility>
#include <queue>
using namespace std;
const int N = 1e6 + 5;
int n, m;
int movie[N]; // 第 i 天放第几部
int weight[N]; // 电影的权值
int pre[N]; // 前面电影的下标, 没有则为 0
int last[N]; // 电影 i 最后出现的下标, 没有则为 0
long long lmax[N << 2], rmax[N << 2], res[N << 2], sum[N << 2]; // 线段树
inline int ls(int u) { return u << 1; }
inline int rs(int u) { return u << 1 | 1; }
inline void push_up(int u) {
lmax[u] = max(lmax[ls(u)], sum[ls(u)] + lmax[rs(u)]);
rmax[u] = max(rmax[rs(u)], sum[rs(u)] + rmax[ls(u)]);
res[u] = max(max(res[ls(u)], res[rs(u)]), lmax[rs(u)] + rmax[ls(u)]);
sum[u] = sum[ls(u)] + sum[rs(u)];
}
// 将 x 处的值改为 val
void modify(int u, int l, int r, int x, int val) {
if(l == r) lmax[u] = rmax[u] = res[u] = sum[u] = val;
else {
int mid = (l + r) >> 1;
if(x <= mid) modify(ls(u), l, mid, x, val);
else modify(rs(u), mid + 1, r, x, val);
push_up(u);
}
}
int main() {
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i ++) scanf("%d", movie + i);
for(int i = 1; i <= m; i ++) scanf("%d", weight + i);
long long ans = -0x3f3f3f3f;
for(int i = 1; i <= n; i ++) {
pre[i] = last[movie[i]], last[movie[i]] = i;
modify(1, 1, n, i, weight[movie[i]]);
if(pre[i]) modify(1, 1, n, pre[i], -weight[movie[i]]);
if(pre[pre[i]]) modify(1, 1, n, pre[pre[i]], 0);
ans = max(ans, res[1]);
}
printf("%lld\n", ans);
return 0;
}
点击查看代码
#include <iostream>
#include <stdio.h>
#include <string.h>
#include <algorithm>
#include <utility>
#include <queue>
using namespace std;
const int N = 1e5 + 5;
int sum[N << 2], add[N << 2];
inline int ls(int u) { return u << 1; }
inline int rs(int u) { return u << 1 | 1; }
inline void push_up(int u) {
sum[u] = sum[ls(u)] + sum[rs(u)];
}
inline void push_down_add(int u, int a, int len) {
sum[u] += a * len, add[u] += a;
}
inline void push_down(int u, int l, int r, int mid) {
push_down_add(ls(u), add[u], l - mid + 1);
push_down_add(rs(u), add[u], r - mid);
add[u] = 0;
}
void clear(int u, int l, int r, int x) {
if(l == r) sum[u] = 0;
else {
int mid = (l + r) >> 1;
push_down(u, l, r, mid);
if(x <= mid) clear(ls(u), l, mid, x);
else clear(rs(u), mid + 1, r, x);
push_up(u);
}
}
void modify(int u, int l, int r, int x, int y, int val) {
if(x <= l && r <= y) push_down_add(u, val, r - l + 1);
else {
int mid = (l + r) >> 1;
push_down(u, l, r, mid);
if(x <= mid) modify(ls(u), l, mid, x, y, val);
if(y > mid) modify(rs(u), mid + 1, r, x, y, val);
push_up(u);
}
}
int query(int u, int l, int r, int x, int y) {
if(x <= l && r <= y) return sum[u];
else {
int mid = (l + r) >> 1, res = 0;
push_down(u, l, r, mid);
if(x <= mid) res += query(ls(u), l, mid, x, y);
if(y > mid) res += query(rs(u), mid + 1, r, x, y);
return res;
}
}
int n = 1e5, m;
int main() {
scanf("%d", &m);
for(int i = 1, l, r; i <= m; i ++) {
scanf("%d%d", &l, &r);
printf("%d\n", query(1, 1, n, l, l) + query(1, 1, n, r, r));
clear(1, 1, n, l), clear(1, 1, n, r);
if(l + 1 <= r - 1) modify(1, 1, n, l + 1, r - 1, 1);
}
return 0;
}
P2572[SCOI2010] 序列操作
点击查看代码
extern "C" {
#include <stdio.h>
#include <assert.h>
}
#include <algorithm>
using std::max; using std::swap;
const int N = 1e5 + 5;
int n, m, a[N];
inline int ls(int u) { return u << 1; }
inline int rs(int u) { return u << 1 | 1; }
struct Node {
int len; // 区间长度
int sum0, sum1; // 0的个数, 1的个数
int max0, max1; // 最长连续0的个数, 最长连续1的个数
int lmax0, lmax1; // 最长以左端点为起点的连续0的个数, 最长以左端点为起点的连续1的个数
int rmax0, rmax1; // 最长以右端点为终点的连续0的个数, 最长以右端点为终点的连续1的个数
int cov:4, rev:4; // 懒标记: 覆盖状况:-1表示没有,0/1表示全为0/1 ,翻转状况: 0/1表示没有翻转/有翻转, 若cov!=-1则rev=0
} tr[N << 2];
int dbl[N << 2], dbr[N << 2]; bool db[N << 2];
Node merge(const Node &l, const Node &r) { // 此时 l.cov=r.cov=-1, l.rev=r.rev=0
return {
l.len + r.len, l.sum0 + r.sum0, l.sum1 + r.sum1,
max(max(l.max0, r.max0), l.rmax0 + r.lmax0), max(max(l.max1, r.max1), l.rmax1 + r.lmax1),
(l.lmax0 == l.len ? l.len + r.lmax0 : l.lmax0), (l.lmax1 == l.len ? l.len + r.lmax1 : l.lmax1),
(r.rmax0 == r.len ? r.len + l.rmax0 : r.rmax0), (r.rmax1 == r.len ? r.len + l.rmax1 : r.rmax1),
-1, 0
};
}
void push_down(int u) {
if(tr[u].cov != -1) {
tr[ls(u)].sum0 = tr[ls(u)].max0 = tr[ls(u)].lmax0 = tr[ls(u)].rmax0 = !tr[u].cov * tr[ls(u)].len;
tr[ls(u)].sum1 = tr[ls(u)].max1 = tr[ls(u)].lmax1 = tr[ls(u)].rmax1 = tr[u].cov * tr[ls(u)].len;
tr[rs(u)].sum0 = tr[rs(u)].max0 = tr[rs(u)].lmax0 = tr[rs(u)].rmax0 = !tr[u].cov * tr[rs(u)].len;
tr[rs(u)].sum1 = tr[rs(u)].max1 = tr[rs(u)].lmax1 = tr[rs(u)].rmax1 = tr[u].cov * tr[rs(u)].len;
tr[ls(u)].cov = tr[rs(u)].cov = tr[u].cov, tr[ls(u)].rev = tr[rs(u)].rev = 0, tr[u].cov = -1;
} else if(tr[u].rev) {
swap(tr[ls(u)].max0, tr[ls(u)].max1), swap(tr[ls(u)].sum0, tr[ls(u)].sum1);
swap(tr[ls(u)].lmax0, tr[ls(u)].lmax1), swap(tr[ls(u)].rmax0, tr[ls(u)].rmax1);
tr[ls(u)].cov == -1 ? tr[ls(u)].rev ^= 1 : tr[ls(u)].cov ^= 1;
swap(tr[rs(u)].max0, tr[rs(u)].max1), swap(tr[rs(u)].sum0, tr[rs(u)].sum1);
swap(tr[rs(u)].lmax0, tr[rs(u)].lmax1), swap(tr[rs(u)].rmax0, tr[rs(u)].rmax1);
tr[rs(u)].cov == -1 ? tr[rs(u)].rev ^= 1 : tr[rs(u)].cov ^= 1;
tr[u].rev = 0;
}
}
void build(int u, int l, int r) {
dbl[u] = l, dbr[u] = r, db[u] = 1;
if(l == r) a[l] ? tr[u] = {1,0,1,0,1,0,1,0,1,-1,0} : tr[u] = {1,1,0,1,0,1,0,1,0,-1,0};
else {
int mid = (l + r) >> 1;
build(ls(u), l, mid);
build(rs(u), mid + 1, r);
tr[u] = merge(tr[ls(u)], tr[rs(u)]);
}
}
void modify(int u, int l, int r, int x, int y, int z) {
if(x <= l && r <= y) {
if(z != -1) {
tr[u].sum0 = tr[u].max0 = tr[u].lmax0 = tr[u].rmax0 = !z * tr[u].len;
tr[u].sum1 = tr[u].max1 = tr[u].lmax1 = tr[u].rmax1 = z * tr[u].len;
tr[u].cov = z, tr[u].rev = 0;
} else {
swap(tr[u].max0, tr[u].max1), swap(tr[u].sum0, tr[u].sum1);
swap(tr[u].lmax0, tr[u].lmax1), swap(tr[u].rmax0, tr[u].rmax1);
tr[u].cov == -1 ? tr[u].rev ^= 1 : tr[u].cov ^= 1;
}
} else {
int mid = (l + r) >> 1;
push_down(u);
if(x <= mid) modify(ls(u), l, mid, x, y, z);
if(y > mid) modify(rs(u), mid + 1, r, x, y, z);
tr[u] = merge(tr[ls(u)], tr[rs(u)]);
}
}
Node query(int u, int l, int r, int x, int y) {
if(x <= l && r <= y) return tr[u];
else {
int mid = (l + r) >> 1;
push_down(u);
if(x <= mid) {
if(y > mid) return merge(query(ls(u), l, mid, x, y), query(rs(u), mid + 1, r, x, y));
else return query(ls(u), l, mid, x, y);
} else return query(rs(u), mid + 1, r, x, y);
}
}
int main() {
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i ++) scanf("%d", a + i);
build(1, 1, n);
for(int i = 1, op, l, r; i <= m; i ++) {
scanf("%d%d%d", &op, &l, &r), l ++, r ++;
if(!op) modify(1, 1, n, l, r, 0);
else if(op == 1) modify(1, 1, n, l, r, 1);
else if(op == 2) modify(1, 1, n, l, r, -1);
else if(op == 3) printf("%d\n", query(1, 1, n, l, r).sum1);
else printf("%d\n", query(1, 1, n, l, r).max1);
}
return 0;
}
P4513 小白逛公园
P1471 方差
P5142 区间方差
P6327 区间加区间sin和
P4588 [TJOI2018]数学计算
建立一个线段树
初始时叶子节点为1
操作一: 将某个叶子节点变为 x
操作二: 将某个叶子节点变为 1
答案: tr[1]
点击查看代码
#include <iostream>
#include <stdio.h>
#include <string.h>
#include <algorithm>
#include <utility>
#include <array>
#include <queue>
using namespace std;
typedef long long LL;
const int N = 1e5 + 5;
int n, mod;
LL tr[N * 4];
inline int ls(int &u) { return u << 1; }
inline int rs(int &u) { return u << 1 | 1; }
void push_up(int u) {
tr[u] = tr[ls(u)] * tr[rs(u)] % mod;
}
void modify(int u, int l, int r, int x, int val) {
if(l == x && r == x) tr[u] = val;
else {
int mid = (l + r) >> 1;
if(x <= mid) modify(ls(u), l, mid, x, val);
else modify(rs(u), mid + 1, r, x, val);
push_up(u);
}
}
int main() {
int T;
scanf("%d", &T);
while(T --) {
scanf("%d%d", &n, &mod);
for(int i = 1; i <= (n << 2); i ++) tr[i] = 1;
for(int i = 1, op, x; i <= n; i ++) {
scanf("%d%d", &op, &x);
if(op == 1) modify(1, 1, n, i, x);
else modify(1, 1, n, x, 1);
printf("%lld\n", tr[1] % mod);
}
}
return 0;
}