线段树

线段树
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 满足:

  1. [xi, yi] ∩ [xj, yj] = {}
  2. li - lj <= L
  3. 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: 每一个节点在被插入时一定满足#1和#2(由递归定义)
    其余时候在左右儿子被交换后左子树一定非空
  2. 最后插入的节点一定是(满足#1和#2的深度最小的节点)或(满足#1和#2的深度最小的节点的左儿子是叶节点时的左儿子)
    2: 如果最后插入的节点不是叶节点且插入后它的某个祖先满足#1和#2
    因为最后插入的节点不是叶节点, 所以插入这个节点时所有经过的节点的左儿子不为空
    则满足#1和#2的祖先在插入后右子树为空->插入前左子树为空(由上行知这个祖先在插入新节点的过程中交换过左右子树)
    则这个祖先之前不满足结论1,矛盾!
  3. 为了保证字典序最小, 如果满足#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;
}
####[Q5.2.6.3. 奇怪的植物](http://139.155.86.76:5283/problem/71356) 思路: 每个植物先查询: 在l的数+在r的数 防止重复: 将其置为0 然后将 [l+1,r-1]上的数+1
点击查看代码

#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;
}
posted @ 2022-10-25 11:18  azzc  阅读(30)  评论(0编辑  收藏  举报