2.基础优化技巧

基础优化技巧1

三分

一般仍然是用二分+eps处理。

01分数规划

求解形如:

\(n\) 个物品,每个物品有两个权值 \(a,b\),选出一些物品,使得两个权值和的比值最大或最小。也就是另 \(w_i=0/1\),最大化:

\[\frac{\sum w_i\times a_i}{\sum w_i\times b_i} \]

一般我们用二分答案处理,移项可得:

\[\sum w_i(a_i-t\times b_i)\ge 0 \]

例题1- [HNOI2009] 最小圈

给定有向带权图,在图上找一个环,使得环上边权和除以节点个数最小,求这个最小的平均值。

\(n\le 3000,m\le 1000,w_i\le 10^7\)

观察到答案是比例形式,联想到 \(0/1\) 分数规划。考虑把边看作 \(a\),点看作 \(b\),二分一个答案后,边权 \(v\) 重新赋值为 \(v-t\),使用 SPFA 找负环即可。找到负环说明 \(mid\) 仍能再小。

判负环注意是入队次数大于等于 \(n\)

写完才发现远古题的恶心之处:

  • 复杂度正确的 BFS SPFA过不了,复杂度错误的 DFS SPFA能过?
  • 题解所有人的算法最差复杂度达到 \(O(m^2n\log )\),这也能过?
  • 4202年还需要洛谷机子加强到 5s,9002年的科技就是发达,1s跑3e9。

整体二分

建议改名整体分治。注意复杂度分析。

例题2- P3527 [POI2011] MET-Meteors

有一个初始为 \(0\) 的序列 \(a\)\(m\) 个操作,每个操作是区间 \([l_j,r_j]\)\(k_j\)。你要对于每个位置 \(i\),求出第几次操作后能使得 \(a_i>x_i\)

\(n\le 3\times 10^5\)

可以主席树二分做,但是空间被卡了。

如果只有一个国家是简单的,直接二分时间+前缀和即可。

考虑对所有国家同时进行二分, 函数 solve(l,r,<vector>v) 表示时间(操作数)的范围是 \([l,r]\),序列是 \(v\)

每次二分一个 mid=l+r>>1,执行 \([1,mid]\) 时间内的操作,看每个点是否超过限制 \(x_i\)。超过则说明需要小一点,放在 \(v_l\) 里,否则放在 \(v_r\) 里。注意树状数组及时清空即可。

以上是常见错误,复杂度退化成单个二分。

实际上整体二分是分治,不知道谁起的这个名字。分治过程中复杂度只能与分治区间有关。树状数组需要通过撤销清空。

复杂度 \(O(n\log^2n)\)

怎么这题还卡 long long

void solve(int l,int r,vector<int>v) {
	int mid=l+r>>1;
	if(l==r) {
		for(int x:v) ans[x]=l;
		return ;
	}
	vector<int>v1,v2;
	for(int i=l;i<=mid;i++) {
		if(op[i].l<=op[i].r)
		t.update(op[i].l,op[i].r,op[i].k);
		else t.update(1,op[i].r,op[i].k),t.update(op[i].l,m,op[i].k); //
	}
	for(int x:v) {
		int tmp=Query(x);
		if(tmp<lim[x]) lim[x]-=tmp,v2.push_back(x);
		else v1.push_back(x);
	}
	for(int i=l;i<=mid;i++) {
		if(op[i].l<=op[i].r)
		t.update(op[i].l,op[i].r,-op[i].k);
		else t.update(1,op[i].r,-op[i].k),t.update(op[i].l,m,-op[i].k); //
	}
	solve(l,mid,v1);solve(mid+1,r,v2);
}

补充例题 P8231 [AGM 2022 资格赛] 农场

将例题2 搬到矩形上

套用二维树状数组即可。

分治

例题3- 【模板】三维偏序

三维偏序,第一维度直接排序做。

然后分治,处理经过分治中点的区间,第二维度左右区间分别归并排序后双指针,注意这样仍满足第一维度的限制,第三维度双指针的时候用权值树状数组在左区间修改,右区间查询。

注意处理相等元素会很麻烦,所以直接去重就好了。记录一下重复元素个数。

bitset 解决 更高维度的偏序

构造01矩阵 \(S_{p}\) 表示第 \(p\) 维度的偏序关系,不难发现 \(S_1({i,j}) \and S_2(i,j)...\) 即为 \(i,j\) 在所有偏序上的关系。只需用 count() 函数计算 bitset\(1\) 的个数即可。

构造 \(S\) 的过程也比较简单,对于每一维先排序,再用 bitset 记录比自己小的集合即可。

时空复杂度 \(O(n^2/\omega),\omega=64\),时间上常数较小,空间上比较紧:**牢记NFLS bitset 惨案:1个长度为 \(N\)bitset 内存是 \(N\times 4/\omega,\omega=32\) **。当 \(N=10^{10}\) 时达到了 1G+,小心MLE保龄。

补充例题 :P3769 [CH弱省胡策R2] TATT

四维偏序

补充例题:P5979 [PA2014] Druzyny

例题4:分治与最短路 - P3350 [ZJOI2016] 旅行者

给你一个 \(N\times M\) 的网格图,边有正边权,\(Q\) 次询问,从 \((x_0,y_0)\to (x_1,y_1)\) 的最短距离。\(N\times M\le 10^4,Q\le 10^5\)

好题!二维题分治一维。

我们知道长成最短路的通常只能用最短路算法(网格图虽说可以 \(dp\),但显然不好做),我们曾做过:[GXOI/GZOI2019] 旅行者,使用二进制分组的方式建虚拟源点跑 \(\log\) 次最短路。那么这道题能否用类似的方式呢?

观察到 \(N\times M\le 10^4\)\(\text{CandyBar}\) 曾在根号重构中讲过类似的处理套路,即 \(\min(N,M)\le 100\)

那么就可以枚举较短的那一条边作为虚拟源点了,外面再套个啥?理应想到分治,因为我们能根号对数时间内快速处理跨过一个点的询问点对。用分治进行降维。

复杂度 \(O(n\sqrt n\log n+Q\sqrt n)\)

例题5:区间最大值分治 - CF1156E Special Segments of Permutation

给定长度为 \(n\) 的排列,求多少区间满足 \(p_l+p_r=\max _{i=l}^r p_i\)

不要往扫描线上想,这是经典序列分治,直接考虑中点,和前缀后缀最大值即可。比较麻烦。

还有一种不大经典的分治,考虑最大值分治,然后最大值所代表的区间是好确立的,我们 只遍历短的那一边,另一边用数据结构 数组查找。

另外可以往笛卡尔树启发式合并上想。

补充例题:CF1175F The Number of Subpermutations

求满足 \(1...r-l+1\) 各出现一次的区间 \([l,r]\)

考虑刻画一个满足条件的区间:

  • 区间最大值为 \(r-l+1\)
  • 区间每个数都只出现了一次。

发现这样足以,前者可以区间最大值分治,后者可以用比较套路的方法:记录上一个出现的位置为 \(p_i\),则 \(\max_{i=1}^rp_i<l\)

注意到区间长度实际上已经确定,因此无论左边右边都直接枚举做即可。

Wa了一次的原因是,枚举左端点,算出右端点,需要保证右端点跨过最大值。

补充例题1:P5979 [PA2014] Druzyny

倍增

常见应用:

  • Floyd 判圈
  • 求LCA / \(k\) 级祖先
  • ST 表

要求:具有结合律,不要求可减性。

例如:\(O(\log n)\) (查询)求树上路径 \(\min,\gcd\)

其正确性由 二进制表示的唯一存在性给出。

例题6:国旗计划

环上给定 \(n\) 个没有包含关系的区间 \([l_i,r_i]\),求必须选第 \(i\) 个区间的情况下,还需要选多少区间才能使得 \([1,n]\) 全部被覆盖。

首先断环为链,对于本题,要么处理区间,要么处理位置。

处理区间的话,我们有显然的贪心策略:如果按右端点排序,跳到最后一个左端点小于当前区间右端点的区间。这个可以简单预处理得到。

然后需要从一个区间不断跳,直到跳到这个区间左端点右边,我们有 \(O(n^2)\) 的模拟,可以倍增优化这个过程。

哈希

通过把某个对象通过对其特征的描述映射为一个值来去重/判断相等。

关键在于抽象其关键信息,以构造适当的哈希函数。

字符串哈希

把字符串看作 B (B>26) 进制数,随机选择一个大质数 \(P\)。结论是两个随机字符串的哈希值相同的概率大约是 \(\frac{1}{P}\)。所以当比较次数较大的时候使用双模哈希。但打CF的时候小心被人对着卡。

自然溢出容易撞的原因是:如果哈希函数构造不当导致很多偶数乘进来,例如结果为一个 \(2^{100}\cdot d\),自然溢出的结果为 \(0\)

例题7 异或哈希:[ABC250E] Prefix Equality

给定两个序列 \(A,B\),多次询问,每次给出 \(x,y\),判断 \(A\) 的前 \(x\) 项和 \(B\) 的前 \(y\) 项构成的集合是否相同。(集合不可重)

显然是和顺序无关的函数,而且我们只在第一次出现的地方修改哈希即可。

常见的套路就是每个值随一个大数,异或起来。

如果拓展到区间,放到主席树上就ok了。

Trie

例题8:P3065 [USACO12DEC] First! G

给定若干字符串,你可以任意规定字符间的偏序关系,求有多少字符串能够成为字典序最小的字符串。

首先只有作为前缀的字符串才有可能是答案。

建出 trie 树,一个串能成为答案,那么从根到这个节点,会形成若干偏序关系,我们需要判断这个偏序关系是否合法。

后者是经典图论模型,偏序关系连边后判断是否成环即可。可以用拓扑排序。

例题9:P4551 最长异或路径

给定一棵树,边有边权,求两点间路径异或和最大是多少。

树上路径异或直接前缀和,甚至不需要求 lca。

问题转换为 \(n\) 个数两两异或和最大是多少。都插到 01trie 里,查询的时候尽可能反着走就行。

注意全局加一是从小往大插,其它都是从大往小插。

例题10:CF888G Xor-MST

有一张 \(n\) 个点的完全图,边权为 \(a_i\oplus a_j\),求MST。

妙妙题。

实际上是 B开头的 MST 算法,考虑初始有 \(n\) 个连通块,每次寻找所有连通块连出去的最小边,将这些边加入最小生成树,并合并这些连通块。

每次连通块个数至少减半,所以复杂度是 \(O(m\log n)\)

对于这个题,我们建出 01trie 后,如何找最小边?这两个点的 lca 一定尽可能深,所以 dfs 一遍tire,从下往上,找到一条最小的边,合并每个点的左右子树。根据 B-算法,这样做是正确的。不需要 dsu 等其他东西。

如何找到最小的边?可以遍历较小的一棵子树,在另一棵子树查询,复杂度类似启发式合并。

也可将原数组排序后再建树,这样树上每个点都对应原数组一段区间。直接遍历即可,每个点只会被其祖先访问,01trie深度是对数级别的。总复杂度 \(O(n\log^2n )\)

posted @ 2024-08-23 18:15  Apricity8211  阅读(3)  评论(0编辑  收藏  举报