数据结构选讲

高级数据结构习题课

主讲人:软2307 刘兆洲

先来几道题目,看看大家伙上周线段树知识掌握了多少?

牛客寒假Day2 H题 Tokitsukaze and Power Battle (hard)image


第一眼,跟维护区间最大字段和同一种方法?

如何维护 \(Big Node\) 的合并?

struct Node {
	ll lmn, rmx, lans, rans, segans, ans, tot;
	Node(ll l1 = 1e9, ll r1 = -1e9, ll l2 = -1e9, ll r2 = -1e9, ll sa = -1e9, ll s = -1e9, ll t = 0) : lmn(l1), rmx(r1), lans(l2), rans(r2), segans(sa), ans(s), tot(t) {}
	inline Node operator + (const Node&rhs) {
		Node ret;
		ret.tot = tot + rhs.tot;
		ret.lmn = min(lmn, tot + rhs.lmn);
		ret.rmx = max(rhs.rmx, rhs.tot + rmx);
		ret.lans = max({lans, tot - rhs.lmn, tot + rhs.lans, segans - rhs.lmn});
		ret.rans = max({rhs.rans, rmx - rhs.tot, rmx + rhs.segans, rans - rhs.tot});
		ret.segans = max({segans - rhs.tot, tot + rhs.segans, tot - rhs.tot});
		ret.ans = max({ans, rhs.ans, rmx - rhs.lmn, rmx + rhs.lans, rans - rhs.lmn});
		return ret;
	}
};

再来一道?

ABC342 G - Retroactive Range Chmax

Problem Statement

​ You are given an integer sequence \(A=(A_1,A_2,\ldots,A_N)\) of length \(N\). Process \(Q\) operations in order. There are three types of operations: - A type-1 operation is represented by a triple of integers \((l,r,x)\), which corresponds to replacing \(A_i\) with \(\max\lbrace A_i,x\rbrace\) for each \(i=l,l+1,\ldots,r\). - A type-2 operation is represented by an integer \(i\), which corresponds to canceling the \(i\)-th operation (it is guaranteed that the \(i\)-th operation is of type 1 and has not already been canceled). The new sequence \(A\) can be obtained by performing all type-1 operations that have not been canceled, starting from the initial state. - A type-3 operation is represented by an integer \(i\), which corresponds to printing the current value of \(A_i\).

Constraints

\(1\leq N\leq2\times10^5\) - \(1\leq A_i\leq10^9\ (1\leq i\leq N)\) - \(1\leq Q\leq2\times10^5\) - In a type-1 operation, \(1\leq l\leq r\leq N\) and \(1\leq x\leq10^9\). - In a type-2 operation, \(i\) is not greater than the number of operations given before, and \(1\leq i\). - In a type-2 operation, the \(i\)-th operation is of type 1. - In type-2 operations, the same \(i\) does not appear multiple times. - In a type-3 operation, \(1\leq i\leq N\). - All input values are integers.


怎么修改?可不可以暴力处理?

const int N = 2e5 + 5;
int n, q, a[N], b[N], X[N], Y[N];

struct SegmentTree {
	multiset <int> t[N << 2];
	inline void build(int pos, int l, int r) {
		if (l == r) {
			t[pos].insert(a[l]);
			return;
		}
		
		int mid = l + r >> 1;
		build(pos << 1, l, mid);
		build(pos << 1 | 1, mid + 1, r);
	}
	
	inline void modify(int pos, int l, int r, int L, int R, int x) {
		if (L <= l && R >= r) {
			t[pos].insert(x);
			return;
		}
		
		int mid = l + r >> 1;
		if (L <= mid) modify(pos << 1, l, mid, L, R, x);
		if (R > mid) modify(pos << 1 | 1, mid + 1, r, L, R, x);
	}
	
	inline void remove(int pos, int l, int r, int L, int R, int x) {
		if (L <= l && R >= r) {
			t[pos].erase(t[pos].find(x));
			return;
		}

		int mid = l + r >> 1;
		if (L <= mid) remove(pos << 1, l, mid, L, R, x);
		if (R > mid) remove(pos << 1 | 1, mid + 1, r, L, R, x);
	}
	
	inline int query(int pos, int l, int r, int x) {
		if (l == r) return *t[pos].rbegin();
		int mid = l + r >> 1, ret;
		if (x <= mid) ret = query(pos << 1, l, mid, x);
		else ret = query(pos << 1 | 1, mid + 1, r, x);
		if (t[pos].size() > 0) chkmax(ret, *t[pos].rbegin());
		return ret;
	}
} T;

死去的记忆开始攻击我了

[六省联考 2017] 相逢是问候

题目描述

Informatik verbindet dich und mich.
信息将你我连结。

B 君希望以维护一个长度为 \(n\) 的数组,这个数组的下标为从 \(1\)\(n\) 的正整数。

一共有 \(m\) 个操作,可以分为两种:

  • 0 l r 表示将第 \(l\) 个到第 \(r\) 个数( \(a_l,a_{l+1} ...a_r\))中的每一个数 \(a_i\) 替换为 \(c^{a_i}\),即 \(c\)\(a_i\) 次方,其中 \(c\) 是输入的一个常数,也就是执行赋值 \(a_i = c^{a_i}\)

  • 1 l r 求第 \(l\) 个到第 \(r\) 个数的和,也就是输出: \(\sum_{i=l}^{r}a_i\)

因为这个结果可能会很大,所以你只需要输出结果 \(\bmod \space p\) 的值即可。

输入格式

第一行有四个整数 \(n, m, p, c\),所有整数含义见问题描述。
接下来一行 \(n\) 个整数,表示 \(a\) 数组的初始值。
接下来 \(m\) 行,每行三个整数,其中第一个整数表示了操作的类型。

  • 如果是 \(0\) 的话,表示这是一个修改操作,操作的参数为 \(l, r\)
  • 如果是 \(1\) 的话,表示这是一个询问操作,操作的参数为 \(l, r\)

输出格式

对于每个询问操作,输出一行,包括一个整数表示答案 \(\bmod \space p\) 的值。

提示

【数据范围】

对于 \(0\%\) 的测试点,和样例一模一样;

对于另外 \(10\%\) 的测试点,没有修改;

对于另外 \(20\%\) 的测试点,每次修改操作只会修改一个位置(也就是 \(l = r\) ),并且每个位置至多被修改一次;

• 对于另外 \(10\%\) 的测试点, \(p = 2\)

对于另外 \(10\%\) 的测试点, \(p = 3\)

对于另外 \(10\%\) 的测试点, \(p = 4\)

对于另外 \(20\%\) 的测试点, \(1\le n,m \le 100\)

对于 \(100\%\) 的测试点, \(1\le n,m \le 5\times 10^4\)\(1 \le p \le 10^8\)\(0< c < p\)\(0 \le a_i < p\)


这是数论题?要用到欧拉函数吗?(申老师应该讲过,不会我不背锅
  • 扩展欧拉定理不必多说,注意好它的分类讨论情况,即指数是否比 \(φ(p)\) 大,若指数比 \(φ(p)\) 小则无法进入指数循环节

  • 很显然每一次的模数都会变为它自己的欧拉函数,而这个过程的次数不会超过对数级别。有一个简单的证明,根据欧拉函数的公式,可以看到一个偶数在求欧拉函数时,一定会把一个素因子2提出来变成1,也就是至少除以2,而一个奇数一定会把自己的一个奇质因子提出来减1,变成一个偶数,接下来就是重复以上过程,直到模数变成1为止。这时任意实数模1都是0,直接返回就好。故P4139的核心代码如下:

const int Maxn = 5e4 + 5, Maxm = 60;
inline int phi(int x) {
	int ret = x;
	for (int i = 2; i * i <= x; i++) {
		if (x % i == 0) ret = ret / i * (i - 1);
		while (x % i == 0) x /= i;
	} return x > 1 ? ret / x * (x - 1) : ret;
}

int n, m, c, P, p[Maxm], cntp = 0, c1[Maxm][1 << 15], c2[Maxm][1 << 15], a[Maxn][Maxm];
// Array c1[i][j] means c ^ j mod p[i] + p[i] and the c2 means c ^ (32768 * j) mod p[i] + p[i]
inline int mo(ll x, int _p) { return x >= _p ? (x % _p + _p) : x; }
inline void setup_array_c(void) {
	for (int i = 0; i <= cntp; i++) {
		c1[i][0] = c2[i][0] = 1;
		for (int j = 1; j < 1 << 15; j++) c1[i][j] = mo(1ll * c1[i][j - 1] * c, p[i]);
		c2[i][1] = mo(1ll * c1[i][(1 << 15) - 1] * c, p[i]); 
		for (int j = 2; j < 1 << 15; j++) c2[i][j] = mo(1ll * c2[i][j - 1] * c2[i][1], p[i]);
	}
}

// c ^ t mod p[i] + p[i]
inline int qpow(int t, int i) { return mo(1ll * c1[i][t & 32767] * c2[i][t >> 15], p[i]); }
inline int calc(int x, int cnt, int i) {
	if (!cnt) return mo(x, p[i]); if (i == cntp) return c ? 1 : 0;
	return qpow(calc(x, cnt - 1, i + 1), i);
}

inline int M(int x, int y, int _p) { return x + y >= _p ? x + y - _p : x + y; }
struct SegmentTree {
	int t[Maxn << 2 | 1], tmin[Maxn << 2 | 1];
	inline void pushup(int pos) {
		tmin[pos] = min(tmin[pos << 1], tmin[pos << 1 | 1]);
		t[pos] = t[pos << 1] + t[pos << 1 | 1];
		if (t[pos] >= P) t[pos] -= P;
	}
	
	inline void build(int pos, int l, int r) {
		if (l == r) { t[pos] = a[l][0]; return; }
		int mid = l + r >> 1;
		build(pos << 1, l, mid), build(pos << 1 | 1, mid + 1, r);
		pushup(pos);
	}
	
	inline void modify(int pos, int l, int r, int L, int R) {
		if (tmin[pos] > cntp) return;
		if (l == r) { t[pos] = a[l][tmin[pos] + 1]; tmin[pos]++; return; }
		int mid = l + r >> 1;
		if (L <= mid) modify(pos << 1, l, mid, L, R);
		if (R > mid) modify(pos << 1 | 1, mid + 1, r, L, R);
		pushup(pos);
	}
	
	inline int query(int pos, int l, int r, int L, int R) {
		if (L <= l && R >= r) return t[pos];
		int mid = l + r >> 1, ret = 0;
		if (L <= mid) ret = query(pos << 1, l, mid, L, R);
		if (R > mid) ret = M(ret, query(pos << 1 | 1, mid + 1, r, L, R), P);
		return ret;
	}
} T;

signed main(void) {
	read(n), read(m), read(P), read(c);
	p[cntp] = P; while (p[cntp] > 1) cntp++, p[cntp] = phi(p[cntp - 1]);
	setup_array_c(); for (int i = 1; i <= n; i++) {
		read(a[i][0]);
		for (int j = 1; j <= cntp + 1; j++) a[i][j] = calc(a[i][0], j, 0) % P;
		a[i][0] %= P;
	} T.build(1, 1, n);
	for (int i = 1, opt, l, r; i <= m; i++) {
		read(opt), read(l), read(r);
		if (opt == 1) writeln(T.query(1, 1, n, l, r));
		else T.modify(1, 1, n, l, r);
	}
//	fwrite(pf, 1, o1 - pf, stdout);
	return 0;
}

依稀当年泪不干~ 组长当年的省队选拔题

[省选联考 2020 A/B 卷] 冰火战士

题目背景

A 卷 D1T1,B 卷 D1T3。

时限 3s,内存 512MB。

题目描述

一场比赛即将开始。

每位战士有两个属性:温度和能量,有两派战士:冰系战士的技能会对周围造成降温冰冻伤害,因而要求场地温度不低于他的自身温度才能参赛;火系战士的技能会对周围造成升温灼烧伤害,因而要求场地温度不高于他的自身温度才能参赛。

当场地温度确定时,双方能够参赛的战士分别排成一队。冰系战士按自身温度从低到高排序,火系战士按自身温度从高到低排序,温度相同时能量大的战士排在前面。首先,双方的第一位战士之间展开战斗,两位战士消耗相同的能量,能量少的战士将耗尽能量退出比赛,而能量有剩余的战士将继续和对方的下一位战士战斗(能量都耗尽则双方下一位战士之间展开战斗)。如此循环,直至某方战士队列为空,比赛结束。

你需要寻找最佳场地温度:使冰火双方消耗总能量最高的温度的最高值。

现在,比赛还处于报名阶段,目前还没有任何战士报名,接下来你将不断地收到报名信息和撤回信息。其中,报名信息包含报名战士的派系和两个属性,撤回信息包含要撤回的报名信息的序号。每当报名情况发生变化(即收到一条信息)时,你需要立即报出当前局面下的最佳场地温度,以及该场地温度下双方消耗的总能量之和是多少。若当前局面下无论何种温度都无法开展比赛(某一方没有战士能参赛),则只要输出 Peace

输入格式

第一行一个数 \(Q\),表示信息的数量。

接下来 \(Q\) 行,每行为 1 t x y \((t \in \{0, 1\}\)\(x\)\(y\) 都是正整数 \()\)2 k\(k\) 是正整数):

1 t x y 表示一条报名信息,\(t = 0\) 时报名战士是冰系,\(t = 1\) 时报名战士是火系,\(x\) 表示战士的自身温度,\(y\) 表示战士的能量。

2 k 表示一条撤回信息,撤回的是第 \(k\) 条信息。被撤回的信息一定是报名信息,已被撤回的信息不会再次被撤回。

输出格式

\(Q\) 行,每行有两个用空格隔开的正整数,分别表示当前局面下的最佳温度和该温度下冰火双方消耗的总能量之和。

提示

数据范围

\(10\%\) 的数据:\(Q \leq 100\)\(x \leq 10^3\)

另有 \(20\%\) 的数据:\(Q \leq 10^4\)\(x \leq 5000\),不存在撤回信息,且输入的 \(x\) 按顺序不降。

\(60\%\) 的数据(包含上述 \(20\%\),下同):\(Q \leq 2 \times 10^5\)\(x \leq 2 \times 10^5\)

\(90\%\) 的数据:\(Q \leq 2 \times 10^6\)\(x \leq 2 \times 10^6\)

\(100\%\) 的数据:\(1 \leq Q \leq 2 \times 10^6\)\(1 \leq x \leq 2 \times 10^9\),所有 \(y\) 之和不超过 \(2 \times 10^9\),保证不存在 \(t, x, y\) 完全相同的两个战士。

题解我懒得码了,大家听我口胡吧


一点新知识 可持久化线段树

https://www.luogu.com.cn/problem/solution/P3919

笔者不打算自己写了,直接搬运。

用处何在?来啦!

The Classic Problem

题面翻译

给定一张 \(n\) 个点,\(m\) 条边的无向图,每条边的边权为 \(2^{x_i}\),求 \(s\)\(t\) 的最短路,结果对 \(10^9+7\) 取模。

\(n, m, x \leq 10^5\)

题目描述

You are given a weighted undirected graph on $ n $ vertices and $ m $ edges. Find the shortest path from vertex $ s $ to vertex $ t $ or else state that such path doesn't exist.

输入格式

The first line of the input contains two space-separated integers — $ n $ and $ m $ ( $ 1<=n<=10^{5} $ ; $ 0<=m<=10^{5} $ ).

Next $ m $ lines contain the description of the graph edges. The $ i $ -th line contains three space-separated integers — $ u_{i} $ , $ v_{i} $ , $ x_{i} $ ( $ 1<=u_{i},v_{i}<=n $ ; $ 0<=x_{i}<=10^{5} $ ). That means that vertices with numbers $ u_{i} $ and $ v_{i} $ are connected by edge of length $ 2^{x_{i}} $ (2 to the power of $ x_{i} $ ).

The last line contains two space-separated integers — the numbers of vertices $ s $ and $ t $ .

The vertices are numbered from $ 1 $ to $ n $ . The graph contains no multiple edges and self-loops.

输出格式

In the first line print the remainder after dividing the length of the shortest path by $ 1000000007 (10^{9}+7) $ if the path exists, and -1 if the path doesn't exist.

If the path exists print in the second line integer $ k $ — the number of vertices in the shortest path from vertex $ s $ to vertex $ t $ ; in the third line print $ k $ space-separated integers — the vertices of the shortest path in the visiting order. The first vertex should be vertex $ s $ , the last vertex should be vertex $ t $ . If there are multiple shortest paths, print any of them.

提示

A path from vertex $ s $ to vertex $ t $ is a sequence $ v_{0} $ , ..., $ v_{k} $ , such that $ v_{0}=s $ , $ v_{k}=t $ , and for any $ i $ from 0 to $ k-1 $ vertices $ v_{i} $ and $ v_{i+1} $ are connected by an edge.

The length of the path is the sum of weights of edges between $ v_{i} $ and $ v_{i+1} $ for all $ i $ from 0 to $ k-1 $ .

The shortest path from $ s $ to $ t $ is the path which length is minimum among all possible paths from $ s $ to $ t $ .


过程中对最短路长度取模合法吗?使用高精度时间复杂度又能承受吗?

可持久化线段树闪亮登场!

考虑优化高精度

注意到边权全为 \(2\) 的幂次,这提示我们用二进制表示数值。

这里设数值 \(val\) 二进制表示下第 \(i\) 位为 \(val(i)\),每次给数值 \(val\)\(2^x\) 相当于找最小的 \(y\) 使得 \(y>=x\)\(val(y)\),然后让 \(x\)\(y\) 之间的 1 归零,\(val(y)\) 置为 1。

归零操作相当于区间覆盖,置 1 的操作相当于单点修改,于是考虑每个数值均用一颗权值线段树维护,寻找 \(y\) 则可以通过线段树二分完成,三种操作都可以单次 \(O(logx)\) 完成。

比较的话就相当于字典序比较,先找到两个数值最高的不相同的位,然后比较那一位的大小即可,这一步就是个经典的字符串算法,用 二分 + 进制 \(hash\) 就可以以单次 \(O(logx)\) 完成。

进制 \(hash\) 满足结合律且可以 \(O(1)\) 快速合并两个子区间的进制 ,于是可以在权值线段树上多加一个变量维护每个节点对应区间的 \(hash\) 值。

进一步的,因为在 \(dijkstra\) 中涉及到 \(O(n+m)\) 个数值,如果每个数值都用一颗权值线段树则炸时空,而单次给数值加上 2 的幂次只会影响权值线段树上 \(O(logx)\) 个节点,显然可以用可持久化优化时空,总时间复杂度为 \(O(mlogmlogx)\)


作业少不了你们滴

P3241 [HNOI2015] 开店(我写过题解) https://www.luogu.com.cn/article/78i90emo
P6492 [COCI2010-2011#6] STEP(我写过题解)https://www.luogu.com.cn/article/3ay35sq8
神秘的NOIP模拟题 https://www.luogu.com.cn/article/qkto87rn
P3437 [POI2006] TET-Tetris 3D (二维线段树,可以自学一下树套树)
P3644 [APIO2015] 巴邻旁之桥 (有两个堆的做法,笔者当年用FHQTreap做的,线段树当然可做)
P2839 [国家集训队] middle (我写过题解)https://www.luogu.com.cn/article/eth10vnb
posted @ 2025-03-21 18:06  EternalEpic  阅读(34)  评论(0)    收藏  举报