From cf07a0b78a443e15e9eea605f2933a498f0278ba Mon Sep 17 00:00:00 2001
From: GoodCoder666 Wolfram Mathematica(简称 MMA),是由 Wolfram Research 开发的科学计算软件。本文我们将介绍 Mathematica 的界面、语法和基本应用。类似的软件还有 MATLAB 和 Maple 等。 MMA 官网:https://www.wolfram.com/mathematica/ MMA 的安装及激活:Mathematica安装激活极简教程 - 科研小飞 (知乎) 本文适合有一定编程基础的读者。当然,如果你不了解编程也没关系,直接跳过相关部分即可。屏幕截图全部来自 Windows 11,Mathematica 13.3。(最新版本 14.0 已经在 2023 年 12 月发布,语法向下兼容) 安装并正确激活 MMA 后,新建笔记本,应该能看到如下的界面: 一张图带你快速熟悉 MMA 的界面: 几个需要注意的点: 对于任意输入或输出,按下Ctrl+Shift+I即可显示输入(代码)形式,按下Ctrl+Shift+N即可显示数学形式。后面会对两种显示形式进行详细讲解。 Mathematica 实质是一个 Wolfram Language 的解释器,所以使用它之前必须学习 WL 的语法。 首先来了解帮助文档的使用。输入 如上,MMA 的注释使用 这两行代码在实际执行中与 在学习 MMA 的函数之前,一定要先学会括号的使用!! MMA 中共有四种括号,分别为 用作对表达式编组和确定运算的优先次序: 表示函数的调用和传参: 表示列表: 列表的具体使用后续会介绍。 表示对列表元素的访问,实质是函数 同样会在后面具体介绍。 MMA 支持基本的数学运算,见下表: 数字和符号都可以参与运算: 表达式是一个或多个运算/函数调用的组合,使用 需要注意的是,MMA 只会对输入的表达式进行约分、合并同类项,而不会自动执行需要展开的化简。必要时可以使用 关于函数的使用,后面会详解。 表达式也可以是等式或不等式: 注意等式用两个等号 这类返回值为 绝大部分布尔表达式都不能自动化简,但可以通过 MMA 中,使用 由上方 用于解方程的 除此之外,还可以用类似于 Python 的语法,同时赋值多个变量: 这行代码可以把变量 分别赋值为 。 若要取消声明一个变量,可以使用 在使用 MMA 的过程中,我们会发现某些特定符号不能声明为变量,且有一个固定不变的值。这些是内置常量: 在前面的讲解中,我们多次提到函数。现在,让我们详细解读函数的使用。 要调用一个函数,只需输入 对于只有一个参数的函数,可以用 这种写法的最大优点在于可以链式调用: 说完了函数的调用,再来说说新函数的定义。 MMA 中,定义新函数的语法为 定义新函数推荐用 拓展 - 定义递归函数 MMA 中有将近 个内置函数[2],它们都以大写字母开头。下面介绍几种常用的内置函数: 数值运算函数 使用示例: MMA 内置了各类三角函数,它们全部使用习惯名称且首字母大写( 解方程 解不等式用 这句话说的是:求解 的范围,使得存在 满足等式 。实际上就是在求解使一元一次方程有解的参数值。注意 如上图中的示例, 偏导 示例略。注意 积分 一个简单的定积分示例: 两个语句分别绘制: 我自己当初学习 MMA 时,被网上杂乱的教程搞得混乱不堪。因此就想写一篇教程,涵盖所有常用语法知识点,并让初学者避开我自己踩的一些坑。 乘法也可简写为 https://www.wolfram.com/language/fast-introduction-for-programmers/en/built-in-functions/ ↩︎
+1. 界面
+
+
+
+
+2. 语法
+
+这里介绍基本语法和常用的指令。2.1 帮助文档
+? Solve 来获取 Solve 函数的说明:
+? 后面可以跟任何函数名,MMA 会直接在笔记本中输出简化版的帮助文档。可以选择输出右上角的 i 来获取更详细的文档(优先打开离线文档,不存在则默认打开在线文档)。菜单栏中的 “帮助 -> Wolfram 参考资料” 可以打开完整版文档。2.2 注释
+
+(* 注释 *)
+(* 和 *) 来标注,用法类似于 C/C++ 中的 /* 和 */。注释可以添加在代码的任何位置,它们会被自动忽略。例如:
+1 + 1 (*Hello*)
+1 + (*World*)1
+1 + 1 无区别:
+2.3 括号
+
+官方参考文档:正确使用括号和大括号()、[]、{} 和 [[]],具体作用如下:圆括号
+()
+方括号
+[]
+大括号
+{}
+双方括号
+[[]]Part 的简写形式:
+2.4 运算与表达式
+
+
+
+
+
+
+
+名称
+符号形式
+函数形式
+数学形式
+
+
+加法
+
+a + b
+Plus[a, b]
+
+
+减法
+
+a - b
+Subtract[a, b]
+
+
+乘法
+
+a * b[1]
+Times[a, b]
+
+
+除法
+
+a / b
+Divide[a, b]
+
+
+幂
+
+a ^ b
+Power[a, b]
+
+
+
+模余
+-
+
+Mod[a, b]
+
+()指定优先级。上图中 4*5、a+a、3 x y/y 都是合法的表达式。Simplify 或 FullSimplify 函数:
+布尔表达式
+
+
+
+== 连接,单个等号表示变量赋值。
+不等式可以用 !=(不等于)、>(大于)、>=(大于等于)、<(小于)、<=(小于等于)连接。!=、>= 和 <= 在输入时会自动转换为相应的数学符号。True(真)或 False(假)的表达式,我们统称为布尔表达式。可以用 && 和 || 运算符来连接两个布尔表达式,分别表示“与”“或”,所得结果仍为布尔表达式。Simplify、FullSimplify 或 Solve、Reduce 来化简或求解:
+2.5 变量与常数
+变量名 = 值 的形式来声明或赋值变量:
+10a + 3 的计算结果可知,表达式中所有已声明的变量都会被替换成变量值。变量的声明也可以包含其他变量和符号,此时仍符合变量替换法则:
+Solve 函数没有返回 的解,而是返回 的解,说明表达式在解析时 被自动替换为了 。
+
+
+{a, b, c} = {1, 2, 3}
+Clear[变量名]:
+
+
+
+
+
+
+
+常量
+名称
+近似值
+数学形式
+
+
+
+Pi圆周率
+
+
+
+
+
+E自然常数
+
+
+
+
+
+I虚数单位
+-
+
+
+
+
+Infinity无穷大
+-
+
+
+
+
+
+Degree度
+-
+
+ 2.6 函数
+调用
+函数名[参数1, 参数2, ...] 即可:
+参数 // 函数名 的形式调用。这种形式常用于 Simplify(化简)、FullSimplify(完全简化)和 N(数值运算)的调用:
+
+定义
+函数名[参数1_, 参数2_, ...] := 返回值。注意每个参数名后面都要加一个下划线(_)。
+
+:=,但使用 = 也可行。
+
+
+
+参考下面定义斐波那契数列的格式:
+
3. 内置函数
+3.1 数值运算 N
+N:
+
+N[expr] 给出 的数值值N[expr, n] 尝试给出具有 位精度的结果
+3.2 三角函数
+Sin、Sinh、ArcSin),这里不一一阐释。
+只说一个注意点,MMA 中三角函数的参数默认是弧度,若要使用角度必须加上 Degree 单位:
+3.3 解方程和不等式 Solve/Reduce
+Solve:
+Reduce:
+Reduce 还有更高级的使用,可以约化各种表达式:
+Solve 和 Reduce 不一定总返回最简形式的表达式,很多情况下需要手动调用 Simplify 或 FullSimplify 进行化简。感兴趣的读者可以自行测试上面的例子中不使用 Simplify 的返回结果。3.4 解方程的其他方法 NSolve/FindInstance
+NSolve 用法同 Solve,但会返回数值解而不是精确解。相当于 Solve[...] // N。
+FindInstance 用法同 Solve,但添加了一个参数表示至多返回解的个数(默认为 ):
+3.5 极点值 Maximize/Minimize
+Minimize 返回函数(在限制条件下)的最小值以及取到最小值的(一种)变量值:
+Minimize 支持多个变量,可以指定条件,也可指定求解域。
+Maximize 用法完全相同,返回最大值,此处略过。3.6 偏导 D
+D 的两种最常用用法:
+
+D[f, x] 给出 关于 的偏导。D[f, {x, n}] 给出 关于 的 阶偏导。f'[x] 可以直接求出 f[x] 的导数:
+3.7 积分 Integrate
+Integrate:
+
+Integrate[f, x] 给出不定积分 。Integrate[f, {x, x_min, x_max}] 给出定积分 。Integrate[f, {x, x_min, x_max}, {y, y_min, y_max}, ...] 给出多重积分 。
+3.8 展开 Expand/ExpandAll
+Expand 很好理解,Expand[expr] 会展开表达式 中的乘积和正整数幂。限于篇幅,这里不提供使用范例,可参考官方文档。
+ExpandAll 在 Expand 的基础上,会展开表达式中任意位置的乘积和整数幂。如表达式 Sin[(1 + x)^3],Expand 不会展开其中的 (1+x)^3,而 ExpandAll 会。3.9 因式分解 Factor
+Factor[poly]:在整数上对一个多项式分解因式。使用示例参考官方文档。3.10 绘图 Plot/Plot3D
+Plot 和 Plot3D 的用法较为复杂,这里只介绍最基本的函数绘图:
+
+
+4. 总结
+
+初衷是写个简明的教程,结果一写就是八千多字…… 也感谢认真读到这里的读者们!
+后续可能还会更一些用 Mathematica 解决数学和实际问题的文章,敬请期待!
+
+
+a b(中间必须有空格)。MMA 中,大部分空格可省略,但是乘法的空格不能省略(若写作没有空格的 ab 会被认为是一个变量)。 ↩︎
🎉 愿你们在新的一年里,万事尽可期待,眼里有光,脸上带笑,心中常怀温暖与爱!🎉
+2023 年的小目标:
+2024 年:
+给定两个正整数 ( 且 ),判断哪个更大。
+模拟即可。
+#include <cstdio>
+using namespace std;
+
+int main()
+{
+ int b, g;
+ scanf("%d%d", &b, &g);
+ puts(b > g? "Bat": "Glove");
+ return 0;
+}
+
+给定 。
+对于任意整数 ,Snuke 都会在数轴上的 处放置一棵圣诞树。
+试问区间 中共有多少棵圣诞树?
+
+
不难发现,对于任意整数 ,数轴上 处有圣诞树当且仅当 。变形可得 ,即 。故只需考虑相对于 的坐标,所以统计 中 的倍数数量即可。
+使用 C++ 语言时,注意正确处理负数的情况。
+#include <cstdio>
+using namespace std;
+
+using LL = long long;
+
+int main()
+{
+ LL a, l, r;
+ int m;
+ scanf("%lld%d%lld%lld", &a, &m, &l, &r);
+ l -= a, r -= a;
+ if(l < 0) l = -((-l) / m * m);
+ else l = (l + m - 1) / m * m;
+ if(r < 0) r = -((-r + m - 1) / m * m);
+ else r = r / m * m;
+ printf("%lld\n", (r - l) / m + 1);
+ return 0;
+}
+
+有长为 的序列 。
+给定 ,将 中数字 各拿掉一个,剩余 个。
+对这 个数进行两两组合(可能剩余 个),使得每对数之差的绝对值之和最小。输出这个最小和。
+
首先,可以证明我们一定会将 个成双的数字进行自我组合。
+++简要证明
+
+采用 反证法。假设有两个 ,我们将它们分别与 组合。显然:+
于是,将 组合、 组合的方案一定不比原方案差。因此直接组合 ,一定能得到最优解。
+
由于 ,所以这部分可以直接忽略,将单个的数字,即 进行组合即可。
+分两种情况讨论。
+为偶数:此时组合没有剩余。不难发现,相邻两两组合即为最优解,所以答案为:
++
直接计算即可。注意 已排序,故无需取绝对值。
+为奇数:此时组合剩余一个。枚举此剩余的数,从 中删去就转换成了偶数的情况。但是暴力计算的时间复杂度为 ,维护前缀后缀和即可优化到 。
+综上,我们在 的时间内解决了此问题。
+#include <cstdio>
+#define maxn 200005
+using namespace std;
+
+inline void setmin(int& x, int y)
+{
+ if(y < x) x = y;
+}
+
+int a[maxn], pre[maxn], suf[maxn];
+
+int main()
+{
+ int n, k;
+ scanf("%d%d", &n, &k);
+ for(int i=1; i<=k; i++)
+ scanf("%d", a + i);
+ if(!(k & 1))
+ {
+ int ans = 0;
+ for(int i=1; i<=k; i+=2)
+ ans += a[i + 1] - a[i];
+ printf("%d\n", ans);
+ return 0;
+ }
+ for(int i=1; i<k; i+=2)
+ pre[i] = pre[i + 1] = pre[i - 1] + a[i + 1] - a[i];
+ for(int i=k-1; i>0; i-=2)
+ suf[i] = suf[i + 1] = suf[i + 2] + a[i + 1] - a[i];
+ int ans = 1e9;
+ for(int i=1; i<=k; i++)
+ {
+ int cur = i & 1? pre[i - 1] + suf[i + 1]
+ : a[i + 1] - a[i - 1] + pre[i - 2] + suf[i + 2];
+ setmin(ans, cur);
+ }
+ printf("%d\n", ans);
+ return 0;
+}
+
+有 个雪橇,编号为 。拉动第 个雪橇需要 只驯鹿。
+给定 次询问,每次给定正整数 :
+注意:雪橇可以任选,每只驯鹿最多只能拉一个雪橇。
+
+
+
首先,为了拉到最多的雪橇,我们考虑贪心的策略:从 最小的雪橇开始拉,从小到大直到驯鹿不够用为止。
+因此我们先对 进行排序,很明显这不影响结果。此时令前缀和 ,则当 时,可以拉动前 个雪橇。故只需找到最大的 使得 即为所求。此时注意到前缀和已经有序,所以直接在 上使用二分查找即可。
+总时间复杂度为 。
+#include <cstdio>
+#include <algorithm>
+#define maxn 200005
+using namespace std;
+
+using LL = long long;
+LL s[maxn];
+
+int main()
+{
+ int n, q;
+ scanf("%d%d", &n, &q);
+ for(int i=0; i<n; i++)
+ scanf("%lld", s + i);
+ sort(s, s + n);
+ for(int i=1; i<n; i++)
+ s[i] += s[i - 1];
+ while(q--)
+ {
+ LL x;
+ scanf("%lld", &x);
+ printf("%d\n", int(upper_bound(s, s + n, x) - s));
+ }
+ return 0;
+}
+
+有一个 的网格,其中.代表红色,#代表绿色。
随机选一个红色方块,将其涂成绿色。将网格抽象成一张简单无向图,边连接相邻(上下左右)的绿色节点。
+图中连通分量个数的期望值是多少?对 取模。
+
暴力算法的时间复杂度是 ,显然不满足要求。
+考虑将一个红色方块涂成绿色对绿色连通分量数的贡献。令它周围属于不同连通分量的绿色方块个数为 ,则此次操作会将答案减去 。这样,我们先预处理出连通分量,就可以 的计算答案。
+此问题可以用 DFS、BFS 或并查集解决。示例代码使用并查集,时间复杂度约为 (忽略小函数)。
+#include <cstdio>
+#include <unordered_map>
+#include <set>
+#include <atcoder/modint>
+#define maxn 1005
+using namespace std;
+
+using modint = atcoder::modint998244353;
+
+int n, m, fa[maxn * maxn];
+char s[maxn][maxn];
+
+int find(int x) { return fa[x] == x? fa[x]: fa[x] = find(fa[x]); }
+inline int calc(int x, int y) { return x * m + y; }
+inline int fc(int x, int y) { return find(calc(x, y)); }
+inline void merge(int x, int y) { fa[find(x)] = find(y); }
+
+int main()
+{
+ scanf("%d%d", &n, &m);
+ for(int i=0; i<n; i++)
+ scanf("%s", s[i]);
+ int k = n * m;
+ for(int i=0; i<k; i++)
+ fa[i] = i;
+ for(int i=0; i<n; i++)
+ for(int j=0; j<m; j++)
+ {
+ if(s[i][j] != '#') continue;
+ if(i && s[i - 1][j] == '#') merge(calc(i, j), calc(i - 1, j));
+ if(j && s[i][j - 1] == '#') merge(calc(i, j), calc(i, j - 1));
+ }
+ int cnt = 0, tot = 0;
+ for(int i=0; i<n; i++)
+ for(int j=0; j<m; j++)
+ if(s[i][j] == '#' && fc(i, j) == calc(i, j))
+ cnt ++;
+ modint ans = 0;
+ for(int i=0; i<n; i++)
+ for(int j=0; j<m; j++)
+ if(s[i][j] == '.')
+ {
+ set<int> S;
+ if(i && s[i - 1][j] == '#') S.insert(fc(i - 1, j));
+ if(s[i + 1][j] == '#') S.insert(fc(i + 1, j));
+ if(j && s[i][j - 1] == '#') S.insert(fc(i, j - 1));
+ if(s[i][j + 1] == '#') S.insert(fc(i, j + 1));
+ int cur = cnt - (int)S.size() + 1;
+ ans += cur, tot ++;
+ }
+ printf("%d\n", (ans / tot).val());
+ return 0;
+}
+
+圣诞老人 Santa 要在平面直角坐标系中给孩子们送礼物啦!
+他的家在 处。他要按照数字顺序给 个孩子送出礼物。第 个孩子的家在 处。
+Santa 手上最多只能一次性拿 个礼物。他想用最短的路程送完所有礼物,再回到自己家,求最短的总路程是多少?
+
+
+
+
这里介绍我自己的独具特(chōu)色(xiàng)的解法,常规解法请参考官方题解。
+考虑 dp,令 表示走到第 个房子并送完前 个礼物时,手上剩余 个礼品的最短路程。很显然,我们每次回家都一定拿满 个礼品,则 一定是拿了礼品之后送出一个。故:
++
其中 表示房子 到 的路程。特别规定 号房子为 ,即圣诞老人的住处。这样,答案即为 。
+直接计算的复杂度为 ,时间和空间上都不能接受。
+然而,仔细观察递推式可以发现, 这一行实际上就是由前一行 删去第一个元素,再整体加 ,并在最后添上 得到的。
+因此,我们可以用一个deque(双端队列)动态维护状态。对于整体加的操作,用一个变量维护整体的变化值即可。这样空间的问题就得到了解决。再进一步考虑,用一个multiset(可重集合,基于红黑树)或者二叉堆维护队列内元素,求 的操作时间就减小到了 ,可以接受。
于是,我们就成功地在 的时间和 的空间内解决了此问题。另外,我们还可以把deque同时充当单调队列,这样时间也优化到了 。两种实现的示例代码都会给出。
#include <cstdio>
+#include <deque>
+#include <set>
+#define maxn 200005
+using namespace std;
+
+using ld = long double;
+const ld INF = 2e18l;
+
+int x[maxn], y[maxn];
+
+inline ld dis(int i, int j)
+{
+ return __builtin_hypotl(x[i] - x[j], y[i] - y[j]);
+}
+
+int main()
+{
+ int n, k;
+ scanf("%d%d", &n, &k);
+ for(int i=0; i<=n; i++)
+ scanf("%d%d", x + i, y + i);
+ deque<ld> f;
+ multiset<ld> s;
+ k --;
+ for(int i=0; i<k; i++)
+ f.push_back(INF), s.insert(INF);
+ f.push_back(dis(0, 1)), s.insert(dis(0, 1));
+ ld dt = 0;
+ for(int i=2; i<=n; i++)
+ {
+ ld lt = *s.begin() + dis(i - 1, 0) + dis(0, i) + dt;
+ s.erase(s.find(f.front())), f.pop_front();
+ dt += dis(i - 1, i), lt -= dt;
+ f.push_back(lt), s.insert(lt);
+ }
+ printf("%.15Lf\n", dt + *s.begin() + dis(n, 0));
+ return 0;
+}
+
+#include <cstdio>
+#include <deque>
+#define maxn 200005
+using namespace std;
+
+int x[maxn], y[maxn];
+
+inline double dis(int i, int j)
+{
+ return __builtin_hypotl(x[i] - x[j], y[i] - y[j]);
+}
+
+int main()
+{
+ int n, k;
+ scanf("%d%d", &n, &k);
+ for(int i=0; i<=n; i++)
+ scanf("%d%d", x + i, y + i);
+ deque<pair<double, int>> f;
+ f.emplace_back(dis(0, 1), 1);
+ double dt = 0;
+ for(int i=2; i<=n; i++)
+ {
+ double lt = f.front().first + dis(i - 1, 0) + dis(0, i) + dt;
+ if(f.front().second == i - k) f.pop_front();
+ dt += dis(i - 1, i), lt -= dt;
+ while(!f.empty() && f.back().first >= lt) f.pop_back();
+ f.emplace_back(lt, i);
+ }
+ printf("%.15lf\n", dt + f.front().first + dis(n, 0));
+ return 0;
+}
+
+有一个 的网格,其中.代表红色,#代表绿色。
随机选一个绿色方块,将其涂成红色。将网格抽象成一张简单无向图,边连接相邻(上下左右)的绿色节点。
+图中连通分量个数的期望值是多少?对 取模。
+
++E 与 G 的区别
++
+- E:将红色涂成绿色。求绿色连通块个数。
+- G:将绿色涂成红色。求绿色连通块个数。
+
注意到本题中红色方块没有任何实质意义,于是先将绿色方块建成一张图。此时题目变为:
+根据“删去无向图中一个点导致连通分量个数改变”,很容易联想到割点。对求割点的 Tarjan 算法稍加改编,就可以计算删去一个点能把图分割成的连通块个数。详见代码。
+#include <cstdio>
+#include <vector>
+#include <atcoder/modint>
+#define maxn 1000005
+using namespace std;
+
+using modint = atcoder::modint998244353;
+
+inline void setmin(int& x, int y)
+{
+ if(y < x) x = y;
+}
+
+vector<int> G[maxn];
+
+inline void add(int x, int y)
+{
+ G[x].push_back(y);
+ G[y].push_back(x);
+}
+
+int root, low[maxn], cnt, dfn[maxn], ncut[maxn];
+
+void tarjan(int v)
+{
+ low[v] = dfn[v] = ++cnt;
+ ncut[v] = v != root;
+ for(int u: G[v])
+ if(!dfn[u])
+ {
+ tarjan(u);
+ if(low[u] >= dfn[v])
+ ncut[v] ++;
+ setmin(low[v], low[u]);
+ }
+ else setmin(low[v], dfn[u]);
+}
+
+char s[1005][1005];
+int id[1005][1005];
+
+int main()
+{
+ int n, m;
+ scanf("%d%d", &n, &m);
+ for(int i=1; i<=n; i++)
+ scanf("%s", s[i] + 1);
+ int num = 0;
+ for(int i=1; i<=n; i++)
+ for(int j=1; j<=m; j++)
+ if(s[i][j] == '#')
+ {
+ id[i][j] = ++num;
+ if(s[i - 1][j] == '#') add(num, id[i - 1][j]);
+ if(s[i][j - 1] == '#') add(num, id[i][j - 1]);
+ }
+ int cc = -1;
+ for(int i=1; i<=num; i++)
+ if(!dfn[i])
+ tarjan(root = i), cc ++;
+ modint ans = 0;
+ for(int i=1; i<=num; i++)
+ ans += cc + ncut[i];
+ printf("%d\n", (ans / num).val());
+ return 0;
+}
+
+首先预祝大家圣诞节快乐 🎉!这场比赛从标题到题目设定,无不与圣诞节有关,AtCoder 官方算是精心准备了这场圣诞庆祝赛 💝。
+遗憾的是我在比赛中先做了 A~E 和 G,F 题比赛结束后 51s 提交,AC。挺可惜的,难得 G 能做出来一次,差点 AK,结果差的就是不到一分钟 😂。希望下次能比得更好,也希望大家能再接再厉。加油!😚
+]]>本题主要考查编码能力,所以直接给出基本思路:
+由以上思路,很容易想到下面三种类型的存储方式:
+用 LL 表示 long long,setmax(x, y) 等同于 x = max(x, y):
inline void setmax(int& x, int y)
+{
+ if(x < y) x = y;
+}
+
+using LL = long long;
+
+定义 struct DataType,表示一种数据类型:
struct DataType
+{
+ const string name; // 类型名
+ LL size, actual_size; // 对齐后的大小和实际大小(有数据的部分的长度)
+ int indent; // 对齐要求
+ vector<pair<DataType*, string>> members; // 类型成员,<成员类型指针,成员名称> 方式存储
+};
+
+对齐操作,依照如下公式:
++
inline LL shift(LL addr)
+{
+ return addr % indent? (addr / indent + 1) * indent: addr;
+}
+
+维护操作,用于操作 后计算大小:
+inline void maintain()
+{
+ size = indent = 0;
+ for(const auto& m: members)
+ {
+ setmax(indent, m.first->indent);
+ size = m.first->shift(size) + m.first->size;
+ }
+ actual_size = size;
+ size = shift(size);
+}
+
+注意 shift 和 maintain 都是 DataType 的成员函数。
主函数中,用一个 unordered_map 记录类型名到数据类型的映射关系:
unordered_map<string, DataType*> types;
+
+添加基本类型:
+auto add_base_type = [&](string name, int size) -> void {
+ DataType* t = new DataType(name);
+ t->size = t->indent = t->actual_size = size;
+ types[name] = t;
+};
+add_base_type("byte", 1);
+add_base_type("short", 2);
+add_base_type("int", 4);
+add_base_type("long", 8);
+
+由于 DataType 中已经实现维护操作,简单处理一下输入即可:
string s;
+int k;
+cin >> s >> k;
+DataType* type = new DataType(s);
+types[s] = type;
+type->members.resize(k);
+for(auto& m: type->members)
+{
+ string t;
+ cin >> t >> m.second;
+ m.first = types[t];
+}
+type->maintain();
+cout << type->size << ' ' << type->indent << '\n';
+
+根据「基本思路」中给出的做法,维护当前第一个可分配的地址和顶层元素列表:
+LL cur_addr = 0LL;
+vector<Object> toplevel_objects;
+
+Object 的定义:
struct Object
+{
+ DataType* type; // 类型
+ string name; // 名称
+ LL addr; // 地址
+};
+
+计算地址并保存元素:
+Object obj;
+string t;
+cin >> t >> obj.name; // 输入
+obj.type = types[t]; // 找到类型指针
+obj.addr = obj.type->shift(cur_addr); // 对齐
+cur_addr = obj.addr + obj.type->size; // 更新可分配的地址
+toplevel_objects.push_back(obj); // 保存元素
+
+输出元素地址:
+cout << obj.addr << '\n';
+
+定义一个辅助函数,类似于 Python 中的 split(),将一个字符串根据指定分隔符分成若干段:
inline void split(const string& s, char sep, vector<string>& res)
+{
+ string t;
+ for(char c: s)
+ if(c == sep)
+ res.push_back(t), t.clear();
+ else t += c;
+ res.push_back(t);
+}
+
+先处理字符串并找到顶层元素:
+// 读入
+string s;
+cin >> s;
+// 分割
+vector<string> ord;
+split(s, '.', ord);
+// 根据名称匹配顶层元素
+LL addr;
+DataType* type;
+for(auto& obj: toplevel_objects)
+ if(obj.name == ord[0])
+ {
+ addr = obj.addr;
+ type = obj.type;
+ break;
+ }
+
+逐层向下,计算地址:
+// ord[0] 对应顶层元素名称,删掉
+ord.erase(ord.begin());
+// 逐层向下遍历
+for(string& s: ord)
+ for(auto& m: type->members)
+ {
+ addr = m.first->shift(addr); // 地址对齐
+ if(m.second == s) // 名称匹配
+ {
+ type = m.first; // 找到下一层,向下遍历
+ break;
+ }
+ addr += m.first->size; // 地址移到下一个元素
+ }
+
+输出最终地址:
+cout << addr << '\n';
+
+同操作 3,先找到顶层元素:
+LL addr;
+cin >> addr;
+if(addr >= cur_addr) // 大于最高有效地址,直接挂掉
+{
+ cout << "ERR\n";
+ continue;
+}
+DataType* type = nullptr;
+LL f_addr = 0LL; // 当前考察的地址
+string res; // 结果字符串
+for(auto& obj: toplevel_objects)
+{
+ if(addr < obj.addr) goto bad; // 特判由于对齐导致的地址无效
+ if(addr < obj.addr + obj.type->size) // 地址在当前范围内,记录结果
+ {
+ type = obj.type;
+ res = obj.name;
+ f_addr = obj.addr;
+ break;
+ }
+}
+
+向下寻找并输出:
+// 循环条件:(1) 地址有效 (2) 不是基本类型(类型有成员)
+while(addr < f_addr + type->actual_size && !type->members.empty())
+ for(auto& m: type->members)
+ {
+ f_addr = m.first->shift(f_addr); // 对齐
+ if(addr < f_addr) goto bad; // 特判,同上
+ if(addr < f_addr + m.first->size)
+ {
+ type = m.first;
+ res.push_back('.');
+ res += m.second;
+ break;
+ }
+ f_addr += m.first->size;
+ }
+if(addr < f_addr + type->actual_size) cout << res << '\n'; // 地址有效则输出结果
+else cout << "ERR\n"; // 地址无效
+continue;
+bad: cout << "ERR\n"; // 前面使用的 bad 标签
+
+下面是赛时代码,也是前面讲解中使用的:
+#include <iostream>
+#include <vector>
+#include <string>
+#include <unordered_map>
+#include <algorithm>
+using namespace std;
+
+inline void setmax(int& x, int y)
+{
+ if(x < y) x = y;
+}
+
+using LL = long long;
+
+struct DataType
+{
+ const string name;
+ LL size, actual_size;
+ int indent;
+ vector<pair<DataType*, string>> members;
+ inline DataType(const string& n): name(n) {}
+ inline LL shift(LL addr)
+ {
+ return addr % indent? (addr / indent + 1) * indent: addr;
+ }
+ inline void maintain()
+ {
+ size = indent = 0;
+ for(const auto& m: members)
+ {
+ setmax(indent, m.first->indent);
+ size = m.first->shift(size) + m.first->size;
+ }
+ actual_size = size;
+ size = shift(size);
+ }
+};
+
+struct Object
+{
+ DataType* type;
+ string name;
+ LL addr;
+};
+
+inline void split(const string& s, char sep, vector<string>& res)
+{
+ string t;
+ for(char c: s)
+ if(c == sep)
+ res.push_back(t), t.clear();
+ else t += c;
+ res.push_back(t);
+}
+
+int main()
+{
+ ios::sync_with_stdio(false); cin.tie(nullptr);
+ unordered_map<string, DataType*> types;
+ auto add_base_type = [&](string name, int size) -> void {
+ DataType* t = new DataType(name);
+ t->size = t->indent = t->actual_size = size;
+ types[name] = t;
+ };
+ add_base_type("byte", 1);
+ add_base_type("short", 2);
+ add_base_type("int", 4);
+ add_base_type("long", 8);
+ int q;
+ cin >> q;
+ vector<Object> toplevel_objects;
+ LL cur_addr = 0LL;
+ while(q--)
+ {
+ int op;
+ cin >> op;
+ if(op == 1)
+ {
+ string s;
+ int k;
+ cin >> s >> k;
+ DataType* type = new DataType(s);
+ types[s] = type;
+ type->members.resize(k);
+ for(auto& m: type->members)
+ {
+ string t;
+ cin >> t >> m.second;
+ m.first = types[t];
+ }
+ type->maintain();
+ cout << type->size << ' ' << type->indent << '\n';
+ }
+ else if(op == 2)
+ {
+ Object obj;
+ string t;
+ cin >> t >> obj.name;
+ obj.type = types[t];
+ obj.addr = obj.type->shift(cur_addr);
+ cur_addr = obj.addr + obj.type->size;
+ toplevel_objects.push_back(obj);
+ cout << obj.addr << '\n';
+ }
+ else if(op == 3)
+ {
+ string s;
+ cin >> s;
+ vector<string> ord;
+ split(s, '.', ord);
+ LL addr;
+ DataType* type;
+ for(auto& obj: toplevel_objects)
+ if(obj.name == ord[0])
+ {
+ addr = obj.addr;
+ type = obj.type;
+ break;
+ }
+ ord.erase(ord.begin());
+ for(string& s: ord)
+ for(auto& m: type->members)
+ {
+ addr = m.first->shift(addr);
+ if(m.second == s)
+ {
+ type = m.first;
+ break;
+ }
+ addr += m.first->size;
+ }
+ cout << addr << '\n';
+ }
+ else // op == 4
+ {
+ LL addr;
+ cin >> addr;
+ if(addr >= cur_addr)
+ {
+ cout << "ERR\n";
+ continue;
+ }
+ DataType* type = nullptr;
+ LL f_addr = 0LL;
+ string res;
+ for(auto& obj: toplevel_objects)
+ {
+ if(addr < obj.addr) goto bad;
+ if(addr < obj.addr + obj.type->size)
+ {
+ type = obj.type;
+ res = obj.name;
+ f_addr = obj.addr;
+ break;
+ }
+ }
+ while(addr < f_addr + type->actual_size && !type->members.empty())
+ for(auto& m: type->members)
+ {
+ f_addr = m.first->shift(f_addr);
+ if(addr < f_addr) goto bad;
+ if(addr < f_addr + m.first->size)
+ {
+ type = m.first;
+ res.push_back('.');
+ res += m.second;
+ break;
+ }
+ f_addr += m.first->size;
+ }
+ if(addr < f_addr + type->actual_size) cout << res << '\n';
+ else cout << "ERR\n";
+ continue;
+ bad: cout << "ERR\n";
+ }
+ }
+ for(auto it=types.begin(); it!=types.end(); it++)
+ delete it->second;
+ return 0;
+}
+
+程序共计 行,长度 ,运行用时 。
+实际上 Object 的定义没有必要,也不需要存储每个顶层元素的地址,同时还可以稍加压行:
#include <bits/stdc++.h>
+using namespace std;
+
+using LL = long long;
+
+struct DataType {
+ const string name;
+ LL size, actual_size;
+ int indent;
+ vector<pair<DataType*, string>> members;
+ inline DataType(const string& n): name(n) {}
+ inline LL shift(LL addr) {
+ return addr % indent? (addr / indent + 1) * indent: addr;
+ }
+ inline void maintain() {
+ size = indent = 0;
+ for(const auto& m: members)
+ {
+ indent = max(indent, m.first->indent);
+ size = m.first->shift(size) + m.first->size;
+ }
+ actual_size = size;
+ size = shift(size);
+ }
+};
+
+inline void split(const string& s, char sep, vector<string>& res) {
+ string t;
+ for(char c: s)
+ if(c == sep) res.push_back(t), t.clear();
+ else t += c;
+ res.push_back(t);
+}
+
+int main() {
+ ios::sync_with_stdio(false); cin.tie(nullptr);
+ unordered_map<string, DataType*> types;
+ auto add_base_type = [&](string name, int size) -> void {
+ DataType* t = new DataType(name);
+ t->size = t->indent = t->actual_size = size;
+ types[name] = t;
+ };
+ add_base_type("byte", 1);
+ add_base_type("short", 2);
+ add_base_type("int", 4);
+ add_base_type("long", 8);
+ int q;
+ cin >> q;
+ vector<pair<DataType*, string>> toplevel_objects;
+ LL cur_addr = 0LL;
+ while(q--) {
+ int op;
+ cin >> op;
+ if(op == 1) {
+ string s;
+ int k;
+ cin >> s >> k;
+ DataType* type = new DataType(s);
+ types[s] = type;
+ type->members.resize(k);
+ for(auto& m: type->members) {
+ string t;
+ cin >> t >> m.second;
+ m.first = types[t];
+ }
+ type->maintain();
+ cout << type->size << ' ' << type->indent << '\n';
+ }
+ else if(op == 2) {
+ string t, name;
+ cin >> t >> name;
+ DataType* type = types[t];
+ cur_addr = type->shift(cur_addr);
+ cout << cur_addr << '\n';
+ cur_addr += type->size;
+ toplevel_objects.emplace_back(type, name);
+ }
+ else if(op == 3) {
+ string s;
+ cin >> s;
+ vector<string> ord;
+ split(s, '.', ord);
+ LL addr = 0LL;
+ DataType* type;
+ for(auto& obj: toplevel_objects) {
+ addr = obj.first->shift(addr);
+ if(obj.second == ord[0]) {
+ type = obj.first;
+ break;
+ }
+ addr += obj.first->size;
+ }
+ ord.erase(ord.begin());
+ for(string& s: ord)
+ for(auto& m: type->members) {
+ addr = m.first->shift(addr);
+ if(m.second == s) {
+ type = m.first;
+ break;
+ }
+ addr += m.first->size;
+ }
+ cout << addr << '\n';
+ }
+ else {
+ LL addr;
+ cin >> addr;
+ if(addr >= cur_addr) {
+ cout << "ERR\n";
+ continue;
+ }
+ DataType* type = nullptr;
+ LL f_addr = 0LL;
+ string res;
+ for(auto& obj: toplevel_objects) {
+ f_addr = obj.first->shift(f_addr);
+ if(addr < f_addr) goto bad;
+ if(addr < f_addr + obj.first->size) {
+ type = obj.first;
+ res = obj.second;
+ break;
+ }
+ f_addr += obj.first->size;
+ }
+ while(addr < f_addr + type->actual_size && !type->members.empty())
+ for(auto& m: type->members) {
+ f_addr = m.first->shift(f_addr);
+ if(addr < f_addr) goto bad;
+ if(addr < f_addr + m.first->size) {
+ type = m.first;
+ res.push_back('.');
+ res += m.second;
+ break;
+ }
+ f_addr += m.first->size;
+ }
+ if(addr < f_addr + type->actual_size) cout << res << '\n';
+ else cout << "ERR\n";
+ continue;
+ bad: cout << "ERR\n";
+ }
+ }
+ for(auto it=types.begin(); it!=types.end(); it++)
+ delete it->second;
+ return 0;
+}
+
+这样只有 行,。
+不过个人觉得写个 Object 更清楚,所以讲解的时候就没改啦~
算法固然重要,但是编码能力也很重要!强烈建议各位 OIer 重视大模拟,不在这种题上挂分~
+写大模拟需要注意的几个点:
+a、b、c、d 到后面自己都不知道是啥,没法调试int 数组祝大家在 NOIP 2023 取得好成绩!求赞qwq
给定一张 个点、 条边的简单无向图 和三个整数 。
+是否存在一条从顶点 到 ,且经过 的简单路径?
+数据范围:
+++什么是 简单路径 ?
+
+简单路径 是不重复经过同一个点的路径。例如, 是简单路径,但 不是简单路径。
不难发现,存在一条 的简单路径,当且仅当存在一条 和一条 的路径,使得这两条路径不经过同一个点( 除外)。因此,我们可以构建网络流模型来解决此问题。
+考虑由 个点组成的有向图 :
+然后进行连边:
+计算 到 的最大流,如果最大流为 则必定有存在不经过同一个顶点的 的路径。
+++证明
+
+显然,如果最大流为 ,必然通过了 和 向汇点连接的边,则一定分别有 和 的路径。
+假设选择的这两条路径经过了同一顶点 ,则两流都必须经过 这一条流量为 的边,此时最大流不可能超过 。而最大流为 ,说明假设不成立,故没有经过同一顶点。
若使用 算法,由于最大流不超过 ,网络流的时间复杂度为 。
+在以下的两种实现中,我们规定
+AC Library 实现
+AtCoder Library 内置最大流的 实现。
+#include <cstdio>
+#include <atcoder/maxflow>
+using namespace std;
+
+int main()
+{
+ int n, m, a, b, c;
+ scanf("%d%d%d%d%d", &n, &m, &a, &b, &c);
+ int s = 0, t = (n << 1) + 1;
+ atcoder::mf_graph<int> G(t + 1);
+ G.add_edge(s, b + n, 2);
+ G.add_edge(a + n, t, 1);
+ G.add_edge(c + n, t, 1);
+ for(int i=1; i<=n; i++)
+ G.add_edge(i, i + n, 1);
+ while(m--)
+ {
+ int x, y;
+ scanf("%d%d", &x, &y);
+ G.add_edge(x + n, y, 1);
+ G.add_edge(y + n, x, 1);
+ }
+ puts(G.flow(s, t, 2) == 2? "Yes": "No");
+ return 0;
+}
+
+Dinic 手写实现
+算法对于此图的时间复杂度为 。如果不清楚算法原理可以参考 OI Wiki。
+++关于空间分配问题
+
+由于新图 包含 条边,若使用静态链式前向星存图,数组大小需要开到 ,其理论最大值为 。此处建议使用 大小的数组。
#include <cstdio>
+#include <cstring>
+#include <queue>
+#include <algorithm>
+#define maxn 400005
+#define maxm 1250005
+using namespace std;
+
+int n, s, t, head[maxn], cur[maxn], dis[maxn],
+ cnt, w[maxm], to[maxm], nxt[maxm];
+
+inline void add(int u, int v, int flow)
+{
+ nxt[cnt] = head[u];
+ head[u] = cnt;
+ to[cnt] = v;
+ w[cnt++] = flow;
+}
+
+inline void add_flow(int u, int v, int f)
+{
+ add(u, v, f);
+ add(v, u, 0);
+}
+
+inline bool bfs()
+{
+ memset(dis, -1, sizeof(int) * n);
+ dis[s] = 0, cur[s] = head[s];
+ queue<int> q;
+ q.push(s);
+ while(!q.empty())
+ {
+ int v = q.front(); q.pop();
+ for(int i=head[v]; ~i; i=nxt[i])
+ if(w[i])
+ {
+ int u = to[i];
+ if(dis[u] == -1)
+ {
+ dis[u] = dis[v] + 1, cur[u] = head[u];
+ if(u == t) return true;
+ q.push(u);
+ }
+ }
+ }
+ return false;
+}
+
+int dfs(int v, int flow)
+{
+ if(v == t) return flow;
+ int res = 0;
+ for(int i=cur[v]; ~i && flow; i=nxt[i])
+ {
+ cur[v] = i;
+ int u = to[i];
+ if(w[i] && dis[u] == dis[v] + 1)
+ {
+ int k = dfs(u, min(flow, w[i]));
+ w[i] -= k;
+ w[i ^ 1] += k;
+ flow -= k;
+ res += k;
+ }
+ }
+ return res;
+}
+
+int main()
+{
+ int n, m, a, b, c;
+ scanf("%d%d%d%d%d", &n, &m, &a, &b, &c);
+ s = 0, t = (n << 1) + 1, ::n = t + 1;
+ memset(head, -1, sizeof(int) * ::n);
+ add_flow(s, b + n, 2);
+ add_flow(a + n, t, 1);
+ add_flow(c + n, t, 1);
+ for(int i=1; i<=n; i++)
+ add_flow(i, i + n, 1);
+ while(m--)
+ {
+ int x, y;
+ scanf("%d%d", &x, &y);
+ add_flow(x + n, y, 1);
+ add_flow(y + n, x, 1);
+ }
+ int mf = 0;
+ while(bfs()) mf += dfs(s, 2);
+ puts(mf == 2? "Yes": "No");
+ return 0;
+}
+
+注意到以下算法的正确性:
+因此,可以使用 算法构造原图的圆方树 来解决此问题。将上述算法转换到圆方树上如下:
+总时间复杂度为 ,实际运行时间优于网络流解法。
+小贴士:圆方树相关的数组要开到两倍大小,不然会 RE 哦~
+#include <cstdio>
+#include <cstdlib>
+#include <vector>
+#define maxn 200005
+using namespace std;
+
+inline void setmin(int& x, int y)
+{
+ if(y < x) x = y;
+}
+
+vector<int> G[maxn], T[maxn << 1];
+
+inline void add_edge(vector<int>* G, int x, int y)
+{
+ G[x].push_back(y);
+ G[y].push_back(x);
+}
+
+int dfc, dfn[maxn], low[maxn], top, st[maxn], cnt;
+
+void tarjan(int v)
+{
+ low[v] = dfn[v] = ++dfc;
+ st[++top] = v;
+ for(int u: G[v])
+ if(!dfn[u])
+ {
+ tarjan(u);
+ setmin(low[v], low[u]);
+ if(low[u] == dfn[v])
+ {
+ add_edge(T, v, ++cnt);
+ do add_edge(T, st[top], cnt);
+ while(st[top--] != u);
+ }
+ }
+ else setmin(low[v], dfn[u]);
+}
+
+int n, m, a, b, c, ct[maxn << 1];
+void dfs(int v, int par)
+{
+ if(v > n)
+ for(int u: T[v])
+ ct[u] ++;
+ if(v == c)
+ {
+ puts(ct[b]? "Yes": "No");
+ exit(0);
+ }
+ for(int u: T[v])
+ if(u != par)
+ dfs(u, v);
+ if(v > n)
+ for(int u: T[v])
+ ct[u] --;
+}
+
+int main()
+{
+ scanf("%d%d%d%d%d", &n, &m, &a, &b, &c);
+ while(m--)
+ {
+ int x, y;
+ scanf("%d%d", &x, &y);
+ add_edge(G, x, y);
+ }
+ cnt = n;
+ tarjan(1);
+ dfs(a, -1);
+ return 0;
+}
+
+三种解法的对比参见下表:
+| 解法 | +代码长度 | +运行时间 | +内存占用 | +
|---|---|---|---|
| 最大流(AC Library)[1] | ++ | + | + |
| 最大流(Dinic)[2] | ++ | + | + |
| 圆方树[3] | ++ | + | + |
可见,圆方树算法的运行速度最快,最大流(AC Library)的代码最短,最大流(Dinic)的内存占用最小。
+++个人评价
+
+这道题出得很好,题意简单而内涵丰富。
+我赛时甚至没想到还可以网络流
给定一个长为 的字符串 ,由 o、-、x 组成。
判断 是否符合下列条件:
+o。x。+
签到题。直接按题意模拟即可。
+#include <cstdio>
+using namespace std;
+
+int main()
+{
+ while(getchar() != '\n');
+ char c;
+ bool ok = false;
+ while((c = getchar()) != '\n')
+ {
+ if(c == 'x')
+ {
+ puts("No");
+ return 0;
+ }
+ if(c == 'o')
+ ok = true;
+ }
+ puts(ok? "Yes": "No");
+ return 0;
+}
+
+++Python
+水题大法速通大法+input() +s = input() +print('Yes' if 'o' in s and 'x' not in s else 'No') +成功省掉个字符(
+逃
给定两个 的矩阵 和 ,都由 和 组成。
+你可以将 顺时针旋转 或 (任选其一)。
+判断旋转后的 能否满足:
++
原题中还贴心的给出了如何将一个矩阵旋转,照题意模拟,旋转次并逐个判断即可。
+#include <cstdio>
+#define maxn 105
+using namespace std;
+
+int a[maxn][maxn], b[maxn][maxn], c[maxn][maxn];
+
+int main()
+{
+ int n;
+ scanf("%d", &n);
+ for(int i=0; i<n; i++)
+ for(int j=0; j<n; j++)
+ scanf("%d", a[i] + j);
+ for(int i=0; i<n; i++)
+ for(int j=0; j<n; j++)
+ scanf("%d", b[i] + j);
+ for(int x=0; x<4; x++)
+ {
+ for(int i=0; i<n; i++)
+ for(int j=0; j<n; j++)
+ c[i][j] = a[i][j];
+ for(int i=0; i<n; i++)
+ for(int j=0; j<n; j++)
+ a[i][j] = c[n - 1 - j][i];
+ for(int i=0; i<n; i++)
+ for(int j=0; j<n; j++)
+ if(a[i][j] && !b[i][j])
+ goto bad; // goto 不是好习惯(改不掉了),千万不要学
+ puts("Yes");
+ return 0;
+ bad:;
+ }
+ puts("No");
+ return 0;
+}
+
+有 个盒子,编号 ,初始均为空。依次处理 次询问:
+1 i j:将数字 写在一张空卡牌上,放入盒子 。2 i:按升序输出盒子 中的所有卡牌(允许重复)。3 i:按升序输出包含卡牌 的所有盒子的编号。若一个盒子里有多张卡牌 ,则这个盒子的编号仅输出一次。+
对于查询中所有卡牌上的数字 ,均有 。
+对于查询中所有的盒子编号 ,均有 。
题目保证输出不超过 个整数。
+我们分别考虑两种输出操作的做法。
+2 i:很容易想到,既然要按升序输出,并且允许重复,我们可以使用 个 multiset 来依次存储每个盒子中的卡牌,处理操作 时更新。3 i:首先不能从 个盒子中依次查找,这样明显会 TLE。正确的做法是,使用 个 set(注意不能重复,所以不用 multiset)分别存储每张卡牌所在的箱子编号,处理操作 时更新。此外,本题也可以使用 priority_queue、map,甚至直接输出时排序并去重,不过使用 set 的方式是最简单、代码量最少的。几种方法的总时间复杂度都是 。
#include <cstdio>
+#include <set>
+#define maxn 200005
+using namespace std;
+
+multiset<int> box[maxn];
+set<int> has[maxn];
+
+int main()
+{
+ int n, q;
+ scanf("%d%d", &n, &q);
+ while(q--)
+ {
+ int op, i;
+ scanf("%d%d", &op, &i);
+ if(op == 1)
+ {
+ int j;
+ scanf("%d", &j);
+ box[j].insert(i);
+ has[i].insert(j);
+ }
+ else if(op == 2)
+ {
+ for(int x: box[i])
+ printf("%d ", x);
+ putchar('\n');
+ }
+ else if(op == 3)
+ {
+ for(int x: has[i])
+ printf("%d ", x);
+ putchar('\n');
+ }
+ }
+ return 0;
+}
+
+我们有一个字符串 。初始时, 1。
处理如下 次询问:
+1 x:将数字 追加至 的最后面。保证 。2:删除 的第一个字符。保证此时 。3:输出 在十进制中对应的数字,对 取模。+
首先,我们必须使用一个 queue 或 deque 来存储字符串 。然后,为了在 的时间内处理第三种操作,我们必须维护 的值,记为 。下面考虑前两种操作对 的影响:
1 x:只需在十进制中腾出一位 再加上 即可,可表示为 。2:先从队列中取出 的第一位,记为 。我们需要从 中减掉最高位乘上其在十进制中的权值,即 (此时 表示队列取出前一位后的长度,等同于取出前的 )对于 的计算,我们可以用一个变量实时维护 的值,也可以预处理出所有 ,或者直接使用快速幂。
+总时间复杂度为 (快速幂)或 (预处理)。
+实现 :使用 AtCoder Library + 快速幂,队列使用 deque
#include <cstdio>
+#include <deque>
+#include <atcoder/modint>
+using namespace std;
+
+using modint = atcoder::modint998244353;
+
+int main()
+{
+ deque<int> s;
+ s.push_back(1);
+ int q;
+ scanf("%d", &q);
+ modint ans = 1;
+ while(q--)
+ {
+ int op;
+ scanf("%d", &op);
+ if(op == 1)
+ {
+ int x;
+ scanf("%d", &x);
+ s.push_back(x);
+ ans = ans * 10 + x;
+ }
+ else if(op == 2)
+ {
+ int x = s.front(); s.pop_front();
+ ans -= x * modint(10).pow((int)s.size());
+ }
+ else printf("%d\n", ans.val());
+ }
+ return 0;
+}
+
+实现 :用变量维护 的值,队列使用 queue
#include <cstdio>
+#include <queue>
+#define MOD 998244353
+using namespace std;
+
+int main()
+{
+ int Q;
+ scanf("%d", &Q);
+ queue<int> q;
+ q.push(1);
+ int ans = 1, p = 1;
+ while(Q--)
+ {
+ int op;
+ scanf("%d", &op);
+ if(op == 1)
+ {
+ int x;
+ scanf("%d", &x);
+ q.push(x);
+ ans = (ans * 10LL + x) % MOD;
+ p = p * 10LL % MOD;
+ }
+ else if(op == 2)
+ {
+ ans -= (long long) q.front() * p % MOD; q.pop();
+ p = p * 299473306LL % MOD; // 299473306 是 10 对于 MOD 的逆元,这句话相当于把 p 除以 10
+ if(ans < 0) ans += MOD;
+ }
+ else printf("%d\n", ans);
+ }
+ return 0;
+}
+
+Takahashi 和 Aoki 将玩一个游戏。游戏规则如下:
+假定 Takahashi 先行,求他赢的概率,对 取模。
+
+
+
考虑概率 DP(下面用 Ta 表示 Takahashi,Ao 表示 Aoki):
+首先考虑初始状态。根据游戏规则,对于任意 ,。
+转移也很显然:
+整理上面的式子,得到:
++
这里注意,由于 的情况无意义(不可能达到),所以无需特殊考虑。
+最终输出结果即为 。总时间复杂度为 。使用前缀和可以优化到 ,有兴趣的可以自己尝试,这里不详细解释了。
+#include <cstdio>
+#include <algorithm>
+#define MOD 998244353
+#define maxn 105
+using namespace std;
+
+using LL = long long;
+inline LL inv(LL x) // x ^ (MOD - 2) % MOD
+{
+ int y = MOD - 2;
+ LL res = 1LL;
+ while(y)
+ {
+ if(y & 1) (res *= x) %= MOD;
+ (x *= x) %= MOD, y >>= 1;
+ }
+ return res;
+}
+
+inline void add(int& x, int y)
+{
+ if((x += y) >= MOD)
+ x -= MOD;
+}
+
+int f[maxn][maxn], g[maxn][maxn];
+
+int main()
+{
+ int n, a, b, p, q;
+ scanf("%d%d%d%d%d", &n, &a, &b, &p, &q);
+ for(int i=0; i<n; i++)
+ f[n][i] = g[n][i] = 1, f[i][n] = g[i][n] = 0;
+ LL prob_p = inv(p), prob_q = inv(q);
+ for(int i=n-1; i>=a; i--)
+ for(int j=n-1; j>=b; j--)
+ {
+ for(int k=1; k<=p; k++)
+ add(f[i][j], g[min(i + k, n)][j]);
+ f[i][j] = f[i][j] * prob_p % MOD;
+ for(int k=1; k<=q; k++)
+ add(g[i][j], f[i][min(j + k, n)]);
+ g[i][j] = g[i][j] * prob_q % MOD;
+ }
+ printf("%d\n", f[a][b]);
+ return 0;
+}
+
+有一个 的网格。令 表示第 行 列的格子()。
+对于 ,整数 被写在 上。在剩余的 个格子里只有数字 。
+你可以选择一个格子 并计算与其同行或同列的 个整数之和 。
+求最大可能的 。
+
+
+
我们令 表示对于 的 ,令 表示第 行的整数之和,表示第 列的整数之和, 表示 上的整数。
+容易发现,。
+然后证明当 最大时,:
+所以,我们可以依次考虑每一行 (),相当于固定了 。这时,我们只需找到一列 (),使得 最大,就可以解决此问题。
+但如果依次考虑所有包含点的列,则最坏情况下时间复杂度为 ,无法通过此题。这时,我们可以使用一个 multiset 或 map 来维护当前每列对答案的贡献()。对于每一行 ,仅需更新这一行上有非 数字的点 的贡献(减去 )即可。
这样,由于每个点会被更新正好一次,所以总时间复杂度为 。
+注意更新完成,求得当前答案后需要复原 map 或 multiset。
#include <cstdio>
+#include <vector>
+#include <set>
+#include <unordered_map>
+using namespace std;
+
+using LL = long long;
+using pii = pair<int, int>;
+
+unordered_map<int, vector<pii>> rows;
+unordered_map<int, LL> col_sum;
+
+template <typename T>
+class MaxSet {
+private:
+ multiset<T> s;
+public:
+ inline void insert(const T& x) { s.insert(x); }
+ inline void update(const T& old, const T& New) {
+ s.erase(s.find(old));
+ s.insert(New);
+ }
+ inline T max() { return *s.rbegin(); }
+};
+
+int main()
+{
+ int n;
+ scanf("%d", &n);
+ while(n--)
+ {
+ int x, y, v;
+ scanf("%d%d%d", &x, &y, &v);
+ rows[x].emplace_back(y, v);
+ col_sum[y] += v;
+ }
+ MaxSet<LL> s;
+ for(auto [_, sum]: col_sum)
+ s.insert(sum);
+ LL ans = 0LL;
+ for(auto& [x, v]: rows)
+ {
+ for(auto [y, val]: v)
+ s.update(col_sum[y], col_sum[y] - val);
+ LL cur = s.max();
+ for(auto [y, val]: v)
+ s.update(col_sum[y] - val, col_sum[y]), cur += val;
+ if(cur > ans) ans = cur;
+ }
+ printf("%lld\n", ans);
+ return 0;
+}
+
+注意本题时间限制为 。
+我们有一块长方形的蛋糕。它可被看作一个 的网格,第 行 列上有 个草莓。
+我们对蛋糕进行 次切分,切成 块。每次切蛋糕可以选择当前的一块,并将其从中间横切或竖切成两块:
+
你想把蛋糕切的尽可能均匀。意思是,令 表示切分完成后每一块上的草莓数量的最大值, 表示最小值,求出 的最小值。
+
+
+
本题解参考官方题解。
+操作完成后得到的蛋糕一定是蛋糕的子矩形,所以最多只有 种数字在剩下的块中。令这些可能的数分别为 。可知 。
+根据上面的 ,我们只需先确定 ,再找到与其对应的最小 ,算出 的最小值即可。
+定义 表示将 的子矩形切成 片时最小可能的每片上草莓数的最大值。
+详见代码。
+#include <bits/stdc++.h>
+using namespace std;
+const long long INF = 1000000000000000000;
+int main(){
+ int H, W, T;
+ cin >> H >> W >> T;
+ vector<vector<long long>> s(H, vector<long long>(W));
+ for (int i = 0; i < H; i++){
+ for (int j = 0; j < W; j++){
+ cin >> s[i][j];
+ }
+ }
+ vector<vector<vector<vector<long long>>>> sum(H, vector<vector<vector<long long>>>(H + 1, vector<vector<long long>>(W, vector<long long>(W + 1, 0))));
+ vector<long long> x;
+ for (int i = 0; i < H; i++){
+ for (int j = i + 1; j <= H; j++){
+ for (int k = 0; k < W; k++){
+ for (int l = k + 1; l <= W; l++){
+ for (int m = i; m < j; m++){
+ for (int n = k; n < l; n++){
+ sum[i][j][k][l] += s[m][n];
+ }
+ }
+ x.push_back(sum[i][j][k][l]);
+ }
+ }
+ }
+ }
+ int cnt = x.size();
+ long long ans = INF;
+ for (int i = 0; i < cnt; i++){
+ vector<vector<vector<vector<vector<long long>>>>> dp(T + 1, vector<vector<vector<vector<long long>>>>(H, vector<vector<vector<long long>>>(H + 1, vector<vector<long long>>(W, vector<long long>(W + 1, INF)))));
+ for (int j = H - 1; j >= 0; j--){
+ for (int k = j + 1; k <= H; k++){
+ for (int l = W - 1; l >= 0; l--){
+ for (int m = l + 1; m <= W; m++){
+ if (sum[j][k][l][m] >= x[i]){
+ dp[0][j][k][l][m] = sum[j][k][l][m];
+ }
+ for (int n = j + 1; n < k; n++){
+ for (int o = 0; o < (n - j) * (m - l); o++){
+ for (int p = 0; p < (k - n) * (m - l) && o + p < T; p++){
+ dp[o + p + 1][j][k][l][m] = min(dp[o + p + 1][j][k][l][m], max(dp[o][j][n][l][m], dp[p][n][k][l][m]));
+ }
+ }
+ }
+ for (int n = l + 1; n < m; n++){
+ for (int o = 0; o < (k - j) * (n - l); o++){
+ for (int p = 0; p < (k - j) * (m - n) && o + p < T; p++){
+ dp[o + p + 1][j][k][l][m] = min(dp[o + p + 1][j][k][l][m], max(dp[o][j][k][l][n], dp[p][j][k][n][m]));
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ ans = min(ans, dp[T][0][H][0][W] - x[i]);
+ }
+ cout << ans << endl;
+}
+
+]]>最近公共祖先简称 LCA(Lowest Common Ancestor)。两个节点的最近公共祖先,就是这两个点的公共祖先里面,离根最远的那个。
+这种算法应用很广泛,可以很容易解决树上最短路等问题。
+为了方便,我们记某点集 的最近公共祖先为 或 。
+++ +部分内容参考 OI Wiki,文章中所有算法均使用C++实现。
+
简单来说,树的邻接表存储就是对于每个结点,存储其能通过一条有向或无向边,直接到达的所有结点。
+传统的存储方式是使用链表(或模拟链表),这样实现比较麻烦,也容易写错。
+此处为了更好的可读性我们使用STL中的可变长度顺序表vector。
#include <vector> // 需要使用STL中的vector
+#define maxn 100005 // 最大结点个数
+
+std::vector<int> G[maxn];
+
+此时,若要添加一条无向边,可使用:
+G[u].push_back(v);
+G[v].push_back(u);
+
+若要添加的有向边:
+G[u].push_back(v);
+
+遍历能直接到达的所有结点:
+for(int u: G[v])
+ cout << u << endl;
+
+对于两种算法,都需要预处理出每个结点的深度。
+一个结点的深度定义为这个结点到树根的距离。
要预处理出所有结点的深度,很简单:
+运用树形dp的方法,令 表示结点 的深度,逐层向下推进:
#include <cstdio>
+#include <vector>
+#define maxn 100005
+using namespace std;
+
+vector<int> G[maxn]; // 邻接表存储
+int depth[maxn]; // 每个结点的深度
+
+void dfs(int v, int par) // dfs(当前结点,父亲结点)
+{
+ int d = depth[v] + 1; // 子结点的深度=当前结点的深度+1
+ for(int u: G[v])
+ if(u != par) // 不加这条判断会无限递归
+ {
+ depth[u] = d; // dp更新子结点深度
+ dfs(u, v); // 往下dfs
+ }
+}
+
+int main()
+{
+ // 构建一张图
+ // ...
+ // 假定图已存入邻接表G:
+ int root = 0; // 默认树根为0号结点,根据实际情况设置
+ dfs(root, -1); // 对于根结点,父亲结点为-1即为无父亲结点
+ return 0;
+}
+
+令 表示两个待求 LCA 的结点。需提前预处理出每个结点的父亲(记结点 的父亲为 )。
+算法步骤:
+时间复杂度分析:
+参考代码:
+#include <cstdio>
+#include <vector>
+#include <algorithm>
+#define maxn 500005
+using namespace std;
+
+vector<int> G[maxn];
+int depth[maxn], par[maxn];
+
+void dfs(int v)
+{
+ int d = depth[v] + 1;
+ for(int u: G[v])
+ if(u != par[v])
+ {
+ par[u] = v, depth[u] = d;
+ dfs(u);
+ }
+}
+
+int lca(int u, int v)
+{
+ if(depth[u] < depth[v])
+ swap(u, v);
+ while(depth[u] > depth[v])
+ u = par[u];
+ while(u != v)
+ u = par[u], v = par[v];
+ return u;
+}
+
+int main()
+{
+ int n, q, root;
+ scanf("%d%d%d", &n, &q, &root);
+ for(int i=1; i<n; i++)
+ {
+ int u, v;
+ scanf("%d%d", &u, &v);
+ G[u].push_back(v);
+ G[v].push_back(u);
+ }
+ par[root] = -1, depth[root] = 0;
+ dfs(root);
+ while(q--)
+ {
+ int u, v;
+ scanf("%d%d", &u, &v);
+ printf("%d\n", lca(u, v));
+ }
+ return 0;
+}
+
+可以发现,程序在最后四个测试点上TLE了:
+这是因为,这四个点是专门针对朴素算法设计的(正好是一个 Subtask),使算法的时间复杂度达到了最坏情况 ,而 ,所以无法通过测试点。当然,朴素算法在随机树上回答 次询问的时间复杂度还是 ,被极端数据卡掉也没办法。
倍增算法是朴素算法的改进算法,也是最经典的 LCA 求法。
+预处理:
++
+
1 的个数」次游标跳转(详见代码)。时间复杂度分析:
+另外倍增算法可以通过交换 fa 数组的两维使较小维放在前面。这样可以减少 cache miss 次数,提高程序效率。
参考代码:
+#include <cstdio>
+#include <vector>
+#include <cmath>
+#define maxn 500005
+using namespace std;
+
+vector<int> G[maxn];
+int fa[maxn][19]; // 2^19=524288
+int depth[maxn];
+
+void dfs(int v, int par)
+{
+ fa[v][0] = par;
+ int d = depth[v] + 1;
+ for(int i=1; (1<<i)<d; i++)
+ fa[v][i] = fa[fa[v][i - 1]][i - 1];
+ for(int u: G[v])
+ if(u != par)
+ depth[u] = d, dfs(u, v);
+}
+
+inline int lca(int u, int v)
+{
+ if(depth[u] < depth[v])
+ u ^= v ^= u ^= v;
+ int m = depth[u] - depth[v];
+ for(int i=0; m; i++, m>>=1)
+ if(m & 1)
+ u = fa[u][i];
+ if(u == v) return u; // 这句不能丢
+ for(int i=log2(depth[u]); i>=0; i--)
+ if(fa[u][i] != fa[v][i])
+ u = fa[u][i], v = fa[v][i];
+ return fa[u][0];
+}
+
+int main()
+{
+ int n, q, root;
+ scanf("%d%d%d", &n, &q, &root);
+ for(int i=1; i<n; i++)
+ {
+ int x, y;
+ scanf("%d%d", &x, &y);
+ G[--x].push_back(--y);
+ G[y].push_back(x);
+ }
+ depth[--root] = 0;
+ dfs(root, -1);
+ while(q--)
+ {
+ int u, v;
+ scanf("%d%d", &u, &v);
+ printf("%d\n", lca(--u, --v) + 1);
+ }
+ return 0;
+}
+
+
+本文详细讲解了 LCA 问题以及求解 LCA 的两种算法。对比如下:
+| 算法 | +预处理时间复杂度 | +单次查询时间复杂度[1] | +空间复杂度 | +能否通过例题[2]? | +
|---|---|---|---|---|
| 朴素算法 | ++ | + | + | ❌ | +
| 倍增算法 | ++ | + | + | ✔️ | +
创作不易,希望大家能给个三连,感谢支持!
+此时间复杂度按照最坏情况计算。 ↩︎
+好久没更算法笔记专栏了,正好学了新算法来更新……
+这也是本专栏的第一个专题问题,涉及到三种数据结构,如果写得有问题请各位大佬多多指教,谢谢!
RMQ 的全称是 Range Minimum/Maximum Query,即区间最大/最小值问题。
+本文中,我们的算法以求最大值为例(最小值基本一致)。题面如下:
+++给定一个长为的序列。
+
+有个询问,第个询问如下:
+给定,求区间的最大值,即。
下面,我们将从暴力算法开始,逐步讲解 RMQ 问题的常用解法。
+通用的 RMQ 问题(除暴力外所有算法都能通过):
+我们先读取序列,再逐个读取询问,对于每个询问直接遍历,最终输出结果。
+总时间复杂度为。
#include <cstdio>
+#define maxn 100005
+using namespace std;
+
+int a[maxn];
+
+int main()
+{
+ int n, q;
+ scanf("%d%d", &n, &q);
+ for(int i=0; i<n; i++)
+ scanf("%d", a + i);
+ while(q--)
+ {
+ int l, r;
+ scanf("%d%d", &l, &r);
+ l --, r --;
+ int res = a[l];
+ for(int i=l+1; i<=r; i++)
+ if(a[i] < res)
+ res = a[i];
+ printf("%d ", res);
+ }
+ return 0;
+}
+
+然而,当你提交到洛谷 P1816时……
+
+肯定还是时间复杂度的锅,算法需要进一步优化。
+Sparse Table(以下称ST表)是用于静态求解 RMQ 问题的数据结构。
+++静态求解是指在原始序列不改变的情况下求解问题。或者说,ST表不能直接进行修改操作。
+
ST表的初始化时间复杂度为,单次查询时间复杂度为。
+ST表的本质是一个的二维数组,其定义如下(令表示原数组,求最小值同理):
++
也就是说,表示从开始,个元素中的最大值。这运用了倍增的思想。
+下面考虑如何快速初始化整个数组。
+根据倍增的常用算法,使用类似于DP的方式填满整个表:
++
填表时,先枚举,再枚举。由于整个表共有大约个状态,而计算一个状态值的时间复杂度为,所以初始化的总时间复杂度为。
+伪代码如下:
+function init() {
+ for i = 1 to N
+ st[i][0] = A[i]
+ for j = 1 to log2(N)
+ for i = 1 to (N + 1 - 2^j)
+ st[i][j] = max(st[i][j - 1], st[i + 2^(j-1)][j - 1])
+}
+
+C++ 实现:
+void init()
+{
+ for(int i=0; i<n; i++)
+ st[i][0] = A[i];
+ for(int j=1; j<=log2(n); j++)
+ for(int i=0; i+(1<<j)<=n; i++) // 注意必须是<=n,1<<j即为2^j
+ st[i][j] = max(st[i][j - 1], st[i + (1 << j - 1)][j - 1]);
+}
+
+对于区间的 RMQ 查询,根据ST表的原理,我们要找到两个区间和,使得它们的并集正好为。即:。
+++为什么是并集?
+
+因为,所以重复的不影响结果。
+如果出现遗漏,且遗漏的正好为最大/最小值,那会影响最终结果,所以不能遗漏。
+如果检查到了多余的元素,且多余的正好大于原区间的最大值,会使查询结果变大,所以不能有多余。
+综上,必须满足和的并集正好为才能查询。
要满足上述条件,我们可以让尽可能靠近,让尽可能靠近,来达到这样的效果。
+此时,我们还需要满足并集的条件,因此我们需要找到最大的,使得,。
则有
++
又因为必须是整数,所以取即可。
+// query(l, r) = max(A[l], ..., A[r - 1])
+inline int query(int l, int r)
+{
+ int k = log2(r - l);
+ return max(st[l][k], st[r - (1 << k)][k]);
+}
+
+下面给出用Sparse Table解决例题的完整代码。总时间复杂度为。
+#include <cstdio>
+#include <cmath>
+#include <algorithm>
+#define maxn 100005
+using namespace std;
+
+int st[maxn][17]; // 2^17=131072
+
+void init(int n)
+{
+ for(int j=1, t=log2(n); j<=t; j++)
+ for(int i=0; i+(1<<j)<=n; i++)
+ st[i][j] = min(st[i][j - 1], st[i + (1 << j - 1)][j - 1]);
+}
+
+inline int query(int l, int r)
+{
+ int k = log2(r - l);
+ return min(st[l][k], st[r - (1 << k)][k]); // 注意此题为min,不是求max
+}
+
+int main()
+{
+ int n, q;
+ scanf("%d%d", &n, &q);
+ for(int i=0; i<n; i++)
+ scanf("%d", st[i]); // 直接读入到ST表中,节约时间和空间
+ init(n);
+ while(q--)
+ {
+ int l, r;
+ scanf("%d%d", &l, &r);
+ // 此处注意因为是左闭右开区间[l,r),所以只有l需要-1
+ printf("%d ", query(--l, r));
+ }
+ return 0;
+}
+
+
+运行时间:
+使用内存:
关于树状数组的原理我已经在这篇文章中讲过,这里不再多说了。下面我们考虑如何应用树状数组解决 RMQ 问题。
+树状数组可以用lowbit操作实现prefixSum(前缀和)以及update(更新)操作,时间复杂度均为。不仅是加法,对于任意满足结合律的运算这两种操作都有效。
我们来简单实现一下支持prefixMax和update操作的树状数组:
#define INF 2147483647
+#define lowbit(x) ((x) & -(x))
+inline void setmax(int& x, int y) { if(y > x) x = y; }
+
+int n, A[N], bit[N];
+
+// max(A[1], ..., A[i])
+inline int prefixMax(int i)
+{
+ int res = -INF;
+ for(; i>0; i-=lowbit(i))
+ setmax(res, bit[i]);
+ return res;
+}
+
+// A[i] = max(A[i], val)
+inline void update(int i, int val)
+{
+ for(; i<=n; i+=lowbit(i))
+ setmax(bit[i], val);
+}
+
+若要初始化树状数组,可以利用update操作进行的初始化:
inline void init()
+{
+ for(int i=1; i<=n; i++)
+ bit[i] = -INF; // 这一段不要忘!
+ for(int i=1; i<=n; i++)
+ update(i, A[i]);
+}
+
+另外,我们也可以用子节点直接更新父节点,达到建树的效果:
+inline void init()
+{
+ for(int i=1; i<=n; i++)
+ bit[i] = A[i];
+ for(int i=1; i<=n; i++)
+ {
+ int j = i + lowbit(i);
+ if(j <= n) setmax(bit[j], bit[i]);
+ }
+}
+
+考虑加法时我们计算rangeSum(区间和)的算法:
inline int rangeSum(int l, int r)
+{
+ return prefixSum(r) - prefixSum(l - 1);
+}
+
+也就是用。
+现在回过来考虑 RMQ 的查询,Min/Max运算不可逆,所以很明显不能用这种计算方式。
+下面我们来介绍针对 RMQ 的树状数组设计。
我们令表示rangeMax(l, r),即的区间最大值。
+令,表示原序列,表示树状数组,考虑如下递推式:
+
++等式 证明
+
+根据树状数组的定义,。
+又有,由于,所以当时,。
++等式 证明
+
+根据操作的结合律可得:。
+这个等式对于任意都成立,但出于时间考虑,我们尽可能使用等式(如果全用等式就退化成了的暴力)。
代码实现:
+int rangeMax(int l, int r)
+{
+ if(l == r) return A[l];
+ int t = r - lowbit(r);
+ return t < l?
+ max(A[r], rangeMax(l, r - 1)):
+ max(bit[r], rangeMax(l, t));
+}
+
+这种查询方式的时间复杂度不好估算,可粗略地记为。实际情况下,运行时间可能稍大于这个值。
+另外,此算法对任意满足结合律的运算(如gcd,lcm)都有效。
下面给出用树状数组解决例题的完整代码。总时间复杂度为[1]。
+#include <cstdio>
+#include <algorithm>
+#define maxn 100005
+using namespace std;
+
+#define lowbit(x) ((x) & -(x))
+
+int a[maxn], bit[maxn];
+
+int rangeMin(int l, int r)
+{
+ if(l == r) return a[l];
+ int t = r - lowbit(r);
+ return t < l?
+ min(a[r], rangeMin(l, r - 1)):
+ min(bit[r], rangeMin(l, t));
+}
+
+inline void init(int n)
+{
+ for(int i=1; i<=n; i++)
+ bit[i] = a[i];
+ for(int i=1; i<=n; i++)
+ {
+ int j = i + lowbit(i);
+ if(j <= n)
+ bit[j] = min(bit[j], bit[i]);
+ }
+}
+
+int main()
+{
+ int n, q;
+ scanf("%d%d", &n, &q);
+ for(int i=1; i<=n; i++)
+ scanf("%d", a + i);
+ init(n);
+ while(q--)
+ {
+ int l, r;
+ scanf("%d%d", &l, &r);
+ printf("%d ", rangeMin(l, r));
+ }
+ return 0;
+}
+
+
+运行时间:
+使用内存:
另外,我们还可以把rangeMin写成非递归的形式,以进一步节省运行时间:
#include <cstdio>
+#define maxn 100005
+using namespace std;
+
+#define INF 2147483647
+#define lowbit(x) ((x) & -(x))
+inline void setmin(int& x, int y) { if(y < x) x = y; }
+
+int a[maxn], bit[maxn];
+
+int rangeMin(int l, int r)
+{
+ int res = INF;
+ while(l <= r)
+ {
+ int t = r - lowbit(r);
+ if(t < l) setmin(res, a[r--]);
+ else setmin(res, bit[r]), r = t;
+ }
+ return res;
+}
+
+inline void init(int n)
+{
+ for(int i=1; i<=n; i++)
+ bit[i] = a[i];
+ for(int i=1; i<=n; i++)
+ {
+ int j = i + lowbit(i);
+ if(j <= n) setmin(bit[j], bit[i]);
+ }
+}
+
+int main()
+{
+ int n, q;
+ scanf("%d%d", &n, &q);
+ for(int i=1; i<=n; i++)
+ scanf("%d", a + i);
+ init(n);
+ while(q--)
+ {
+ int l, r;
+ scanf("%d%d", &l, &r);
+ printf("%d ", rangeMin(l, r));
+ }
+ return 0;
+}
+
+
+运行时间:
+使用内存:
线段树和树状数组一样,都是解决区间问题的树状结构。不过线段树的应用范围更加广泛,时间复杂度与树状数组基本一致,但每种操作都有一个之间的常数。线段树建树(初始化)的时间复杂度为,单次区间查询的时间复杂度为。
+RMQ 问题不涉及修改操作,因此我们暂时不考虑这种操作。
+
+如图即为的一棵线段树。可以发现,线段树本质上是一棵二叉树,每个结点代表一个区间,其存储的值为这个区间的区间和(在 RMQ 问题中为区间最大/最小值)。一个结点的左儿子结点和右儿子结点对应区间的并集正好为这个结点对应的区间(叶子结点除外),且左右两区间的长度的差值不超过。
+从树的角度考虑,,即没有子结点数量为的结点。
+一般的,若一个结点对应的区间为:
+++顺便纠正一下线段树的几个误区:
+
+线段树是一棵完全二叉树。
+请仔细看看图。
+线段树是一棵二叉搜索树。
+反正我是没找到哪里这样定义的。百度百科 OI Wiki
+线段树上同一深度的结点所对应的区间长度一定相等。
+看看图,和两个区间,长度明显不相等。
+特例:当是的整数次幂时,这句话一定成立。
线段树采用堆式储存法,根结点为,结点的父亲为,左子结点为,右子结点为。
+可以用位运算优化:
+u >> 1u << 1u << 1 | 1(或u << 1 ^ 1)// 数组定义
+int a[N], c[4 * N];
+
+// 宏定义
+#define INF 2147483647
+#define ls(x) (x << 1) // 左儿子结点
+#define rs(x) (x << 1 | 1) // 右儿子结点
+#define par(x) (x >> 1) // 父亲结点
+
+下文中,我们令表示元素个数,表示原数组,表示线段树(数组形式存储)。
+可以证明,线段树的结点个数不会超过,所以我们可以把的长度设为。
对于一个结点数据的计算,我们可以先递归地初始化其左子树,再到右子树,最后两儿子的数据取即可。
+代码实现:
+// 结点p, 区间[l, r]建树
+void build(int l, int r, int p)
+{
+ if(l == r)
+ {
+ c[p] = a[l];
+ return;
+ }
+ int m = l + r >> 1;
+ build(l, m, ls(p));
+ build(m + 1, r, rs(p));
+ c[p] = max(c[ls(p)], c[rs(p)]);
+}
+
+同样采用递归的方式,设为当前结点,为当前结点区间,为当前查询区间,函数返回和的交集的区间和:
+如果上面不是很好理解,可以直接看代码实现:
+// 结点p, 查询区间[a, b], 当前区间[l, r]
+int query(int l, int r, int a, int b, int p)
+{
+ if(a <= l && r <= b) return c[p];
+ int m = l + r >> 1, res = -INF;
+ if(m >= a) res = max(res, query(l, m, a, b, ls(p)));
+ // m + 1 <= b 即为 m < b
+ if(m < b) res = max(res, query(m + 1, r, a, b, rs(p)));
+ return res;
+}
+
+下面给出用线段树解决例题的完整代码。总时间复杂度为。
+#include <cstdio>
+#include <algorithm>
+#define maxn 100005
+using namespace std;
+
+#define INF 2147483647
+#define ls(x) (x << 1)
+#define rs(x) (x << 1 | 1)
+#define par(x) (x >> 1)
+
+int a[maxn], c[maxn << 2];
+
+void build(int l, int r, int p)
+{
+ if(l == r)
+ {
+ c[p] = a[l];
+ return;
+ }
+ int m = l + r >> 1;
+ build(l, m, ls(p));
+ build(m + 1, r, rs(p));
+ c[p] = min(c[ls(p)], c[rs(p)]);
+}
+
+int query(int l, int r, int a, int b, int p)
+{
+ if(a <= l && r <= b) return c[p];
+ int m = l + r >> 1, res = INF;
+ if(m >= a) res = min(res, query(l, m, a, b, ls(p)));
+ if(m < b) res = min(res, query(m + 1, r, a, b, rs(p)));
+ return res;
+}
+
+int main()
+{
+ int n, q;
+ scanf("%d%d", &n, &q);
+ for(int i=0; i<n; i++)
+ scanf("%d", a + i);
+ build(0, n - 1, 1);
+ while(q--)
+ {
+ int l, r;
+ scanf("%d%d", &l, &r);
+ printf("%d ", query(0, n - 1, --l, --r, 1));
+ }
+ return 0;
+}
+
+
+运行时间:
+使用内存:
我们来对比一下四种算法,从理论的角度:
+| 算法 | +预处理时间复杂度 | +单次查询时间复杂度 | +空间复杂度 | +符合题目要求?[2] | +
|---|---|---|---|---|
| 暴力 | ++ | + | + | ❌ | +
| ST表 | ++ | + | + | ✔️ | +
| 树状数组 | +或 | ++ | + | ✔️ | +
| 线段树 | ++ | + | + | ✔️ | +
从洛谷 P1816上实际运行情况的角度(暴力TLE,不考虑):
| 算法 | +运行时间 | +内存占用 | +代码长度 | +
|---|---|---|---|
| ST表 | ++ | + | + |
| 树状数组(递归)[3] | ++ | + | + |
| 树状数组(非递归)[3:1] | ++ | + | + |
| 线段树 | ++ | + | + |
可以看出,ST表写起来简单省事,运行时间也是最快,不过内存占用稍高,毕竟空间复杂度是;
+树状数组可以说是均衡了时间与空间,尽量使用非递归查询,比递归速度快,当然如果想省事也可以使用递归式查询;
+线段树可以说是完全输给了树状数组(非递归),不过线段树功能比较多,用来做 RMQ 可以说是大材小用。所以线段树在 RMQ 问题中没什么优势,有一些缺点还是可以理解的。
本文到此结束,希望大家给个三连!
+这也是我在年写的第一篇文章(也是我的第一篇万字长文),祝大家新年快乐!
给定整数,输出,保留三位小数。
+
+
签到题,使用printf或cout格式化输出即可。
#include <cstdio>
+using namespace std;
+
+int main()
+{
+ int a, b;
+ scanf("%d%d", &a, &b);
+ printf("%.3Lf\n", (long double)b / a);
+ return 0;
+}
+
+给定一个的网格,每个方格内都是.或#。
+求每一列的#的个数,分别输出。
+
开一个数组ans[W],存储每一列的#的个数。输入时统计一下即可。
#include <cstdio>
+#define maxn 1005
+using namespace std;
+
+char s[maxn];
+int ans[maxn];
+
+int main()
+{
+ int n, m;
+ scanf("%d%d", &n, &m);
+ while(n--)
+ {
+ scanf("%s", s);
+ for(int i=0; i<m; i++)
+ if(s[i] == '#')
+ ans[i] ++;
+ }
+ for(int i=0; i<m; i++)
+ printf("%d ", ans[i]);
+ return 0;
+}
+
+有一棵由个结点组成的树,根结点是。
+整棵树用一个序列表示:
+求每个结点的深度。
+
+
根据题意构造树的邻接表,从根结点开始向下搜索,从而推出每个结点的深度。
+#include <cstdio>
+#include <vector>
+#define maxn 200005
+using namespace std;
+
+vector<int> G[maxn << 1];
+int dep[maxn << 1];
+
+void dfs(int v, int par)
+{
+ for(int u: G[v])
+ if(u != par)
+ {
+ dep[u] = dep[v] + 1;
+ dfs(u, v);
+ }
+}
+
+int main()
+{
+ int n;
+ scanf("%d", &n);
+ for(int i=1; i<=n; i++)
+ {
+ int x;
+ scanf("%d", &x);
+ G[x].push_back(i << 1);
+ G[x].push_back(i << 1 | 1);
+ }
+ dep[1] = 0;
+ dfs(1, -1);
+ for(int i=1; i<=(n<<1)+1; i++)
+ printf("%d\n", dep[i]);
+ return 0;
+}
+
+我们从解法进一步考虑:由于,所以一定在和前被处理,那么直接在输入时计算depth[2*i] = depth[2*i+1] = depth[A[i]] + 1即可。
#include <cstdio>
+#include <vector>
+#define maxn 200005
+using namespace std;
+
+int dep[maxn << 1];
+
+int main()
+{
+ int n;
+ scanf("%d", &n);
+ for(int i=1; i<=n; i++)
+ {
+ int x;
+ scanf("%d", &x);
+ dep[i << 1] = dep[i << 1 | 1] = dep[x] + 1;
+ }
+ for(int i=1; i<=(n<<1)+1; i++)
+ printf("%d\n", dep[i]);
+ return 0;
+}
+
+给定整数和序列,能否在平面直角坐标系中通过步从走到?每一步如下:
+
+
+
先考虑另一个问题:
+++在一维坐标系中,从开始进行次位移,第次的操作如下:
+
+选择左移或者右移个长度单位,即坐标加上或者减去。
+次操作后是否能到达终点?注意:必须为最终到达,中途经过不算数!
很容易想到使用一个简单的,令表示前次操作后是否能达到(或),转移显而易见:。
+但是这样的时间复杂度很高,高达,其中为坐标系大小。
稍加思考会发现,只有小部分坐标能真正达到,其余都没有必要参与转移,所以使用set进行存储,表示前次操作后能到达的坐标集合,利用进行转移即可。
代码:
+inline bool check(vector<int>& v, int start, int target)
+{
+ set<int> s;
+ s.insert(start);
+ for(int d: v)
+ {
+ set<int> ls = s;
+ s.clear();
+ for(int x: ls)
+ s.insert(x + d), s.insert(x - d);
+ }
+ return s.count(target);
+}
+
+然后回到原来的问题,发现由于和两个坐标互不影响,所以把两个坐标轴分别独立出来是没有问题的,可以转换为刚才的子问题:
+只要两个子问题的条件都满足,那么一定存在一种可行的操作序列来满足原题的要求。
+至此,问题得到解决。
#include <cstdio>
+#include <vector>
+#include <set>
+using namespace std;
+
+inline bool check(vector<int>& v, int start, int target)
+{
+ set<int> s;
+ s.insert(start);
+ for(int d: v)
+ {
+ set<int> ls = s;
+ s.clear();
+ for(int x: ls)
+ s.insert(x + d), s.insert(x - d);
+ }
+ return s.count(target);
+}
+
+int main()
+{
+ int n, x, y;
+ scanf("%d%d%d", &n, &x, &y);
+ vector<int> a(n);
+ for(int& t: a) scanf("%d", &t);
+ vector<int> dx;
+ for(int i=2; i<n; i+=2)
+ dx.push_back(a[i]);
+ if(!check(dx, a[0], x)) { puts("No"); return 0; }
+ vector<int> dy;
+ for(int i=1; i<n; i+=2)
+ dy.push_back(a[i]);
+ puts(check(dy, 0, y)? "Yes": "No");
+ return 0;
+}
+
+在平面直角坐标系中,有个城市和个箱子。城市位于坐标,箱子则在坐标。
+Takahashi现在要从原点开始访问个城市,中途箱子可去可不去。他初始的速度为,每碰到一个箱子都可以将速度提升至原先的两倍(每个箱子只能加速一次)。
+至少要用多少时间,才能将个城市都访问至少一次?
+参考AtCoder 官方题解的做法,这里不详细解释。
+#include <cstdio>
+#include <cmath>
+#define maxn 17
+using namespace std;
+
+inline double ppow(int x) { return 1.0 / (1 << __builtin_popcount(x)); }
+inline void setmin(double& x, double y)
+{
+ if(y < x) x = y;
+}
+
+double x[maxn], y[maxn], dp[maxn][1 << maxn];
+
+int main()
+{
+ // Input
+ int n, m;
+ scanf("%d%d", &n, &m);
+ m += n;
+ for(int i=0; i<m; i++)
+ scanf("%lf%lf", x + i, y + i);
+ int mx = 1 << m;
+ for(int i=0; i<m; i++)
+ for(int s=0; s<mx; s++)
+ dp[i][s] = 1e18;
+
+ // DP: Initial state
+ for(int i=0; i<m; i++)
+ dp[i][1 << i] = hypot(x[i], y[i]);
+
+ // DP: Transfer
+ for(int s=1; s<mx; s++)
+ {
+ double coef = ppow(s >> n);
+ for(int i=0; i<m; i++)
+ {
+ if(!(s >> i & 1)) continue;
+ for(int j=0; j<m; j++)
+ {
+ if(s >> j & 1) continue;
+ setmin(dp[j][s | (1 << j)],
+ dp[i][s] + hypot(x[i] - x[j], y[i] - y[j])*coef);
+ }
+ }
+ }
+
+ // Output
+ double ans = 1e18;
+ for(int i=0, t=1<<n; i<m; i++)
+ for(int s=t-1; s<mx; s+=t)
+ setmin(ans, dp[i][s] + dp[i][1 << i] * ppow(s >> n));
+ printf("%.10f\n", ans);
+ return 0;
+}
+
+]]>突然想到位运算是个好东西,就来水一波文章了……
+注意:我把能想到的有关位运算的所有内容都放进来了,所以篇幅较长,请谅解!若有写的不清楚或者不够详细的地方欢迎在评论区补充,谢谢支持!
+本文中参考代码均使用C++编写。
+废话不多说,下面步入正题。
+有一定基础的可以跳过该部分。
+位运算的简要法则:
+
详细解释:
+取反(~x)是最简单的位运算操作,只有一个参数。将参数上的每一位对应取反即可。例如:
+~0011 = 1100
+~1011 = 0100
+性质:~(~x) = x
按位与(x & y)有两个参数和。对于和中的每个对应位,参照下表输出到结果的对应位:
| + | + | x & y |
+
|---|---|---|
| + | + | + |
| + | + | + |
| + | + | + |
| + | + | + |
例子:
+0011 & 1100 = 0000
+1010 & 1011 = 1010
性质:
+a & b = b & aa & b & c = a & (b & c)a & a = a0 & a1 & a2 & a3 & ... = 0a & inf = a按位与(x | y)有两个参数和。对于和中的每个对应位,参照下表输出到结果的对应位:
| + | + | x | y |
+
|---|---|---|
| + | + | + |
| + | + | + |
| + | + | + |
| + | + | + |
例子:
+1100 | 0011 = 1111
+1010 | 0001 = 1011
性质:
+a | b = b | aa | b | c = a | (b | c)a | a = aa | 0 = aa | inf = inf异或(或x ^ y)有两个参数和。对于和中的每个对应位,参照下表输出到结果的对应位:
| + | + | + |
|---|---|---|
| + | + | + |
| + | + | + |
| + | + | + |
| + | + | + |
举例:
+1000 ^ 1011 = 0011
+0101 ^ 1010 = 1111
性质:
+~a位移分为左移(<<)和右移(>>)。
a << b:将末尾添上个的结果。a >> b:从末尾删掉位的结果。性质:
+(a << b) >> b = aa << ba >> b题意:给定整数,判断其是否为的整数次幂。
+题意:给定一个位整数,在二进制下交换其前位与后位,输出最终的数。
+答案为ans = (x >> 16) | (x << 16),这样解释:
| 数值 | +前位 | +后位 | +
|---|---|---|
| + | + | + |
x >> 16 |
+个 | ++ |
x << 16 |
++ | 个 | +
ans |
++ | + |
注意此处使用位无符号整数进行计算,这样x << 16会自然溢出,导致前位被丢弃,恰好满足要求。
参考程序:
+#include <cstdio>
+using namespace std;
+
+int main()
+{
+ unsigned int x;
+ scanf("%u", &x);
+ printf("%u\n", (x >> 16) | (x << 16));
+ return 0;
+}
+
+给定一个序列,其中有个数各出现次,还有一个数正好出现次。找到这个数。请尽可能优化程序的时间和空间复杂度。
+- 时间或,空间解法
+简单统计每个数的出现次数,最后找到正好出现次的数。
- 时间,空间解法
+考虑所有数的异或和,则中所有出现两次的数抵消为,剩下的即为唯一出现一次的数,所以直接输出即可。
参考程序:
+#include <cstdio>
+using namespace std;
+
+int main()
+{
+ int n;
+ scanf("%d", &n);
+ n = (n << 1) + 1;
+ int ans = 0;
+ while(n--)
+ {
+ int x;
+ scanf("%d", &x);
+ ans ^= x;
+ }
+ printf("%d\n", ans);
+ return 0;
+}
+
+解法:对于,记录操作后每一位上的和分别变成什么,可以在的时间内用类似于前缀和的方法完成;最后用位运算快速模拟次连续操作即可,总时间复杂度为。
+// https://atcoder.jp/contests/abc261/submissions/33495431
+#include <cstdio>
+using namespace std;
+
+int main()
+{
+ unsigned n, c, zero = 0, one = 0xffffffff;
+ scanf("%d%d", &n, &c);
+ while(n--)
+ {
+ int t, a;
+ scanf("%d%d", &t, &a);
+ if(t == 1) one &= a, zero &= a;
+ else if(t == 2) one |= a, zero |= a;
+ else one ^= a, zero ^= a;
+ printf("%d\n", c = (c & one) | (~c & zero));
+ }
+ return 0;
+}
+
+lowbit(x)即为二进制下的最低位,如lowbit(10010) = 10、lowbit(1) = 1。严格来说没有lowbit,部分情况下可视为lowbit(0) = 1。利用lowbit函数可实现树状数组等数据结构。
lowbit 计算方式
+int lowbit(int x)
+{
+ int res = 1;
+ while(x && !(x & 1))
+ x >>= 1, res <<= 1;
+ return res;
+}
+
+时间复杂度。缺点:速度慢,代码长,没有体现位运算的优势x & -xlowbit(x) = x & -x。感兴趣的读者可自行尝试证明。x & (x - 1)x & (x - 1)不是lowbit(x),而是x - lowbit(x)。x - lowbit(x)的计算速度。popcount(x)定义为在二进制下的个数,如popcount(10101) = 3,popcount(0) = 0。
popcount 计算方式
+int popcount(int x)
+{
+ int res = 0;
+ while(x)
+ {
+ res += x & 1;
+ x >>= 1;
+ }
+ return res;
+}
+
+lowbit 优化int popcount(int x)
+{
+ int res = 0;
+ for(; x; x&=x-1) res ++;
+ return res;
+}
+
+3.1 __builtin_popcount/__builtin_popcountll。注意:后面带ll的传入long long类型,不带ll接受int类型。本部分内容按常用程度递减排序。
+参考:https://blog.csdn.net/zeekliu/article/details/124848210
返回参数在二进制下的个数。
+返回参数在二进制下末尾的个数。
+返回参数在二进制下前导的个数。
+返回参数在二进制下最后一个1在第几位(从后往前)。
+注意:一般来说,builtin_ffs(x) = __builtin_ctz(x) + 1。当时,builtin_ffs(x) = 0。
返回参数在二进制下的个数的奇偶性(偶:0,奇:1),即__builtin_parity(x) = __builtin_popcount(x) % 2。
+P.S. 这函数,不知是哪位神仙想出来的……
对于集合,我们使用一个位的二进制整数来表示它的一个子集。从右往左第位表示子集是否包含了。容易发现,对于任意子集,,且对于任意,都是的一个有效子集。下面我们来讲这种子集表示的具体操作。
+子集的操作如下(规定为集合元素个数):
+__builtin_popcount(S)或__builtin_popcountll(S)S >> i & 1S |= 1 << iS ^= 1 << iS &= ~(1 << i)S & TS | TS ^ T讲了这么多,也该到子集的实际应用了吧。下面我们来看子集最初步的应用——子集枚举。
+- 必会:枚举个元素的所有子集
+这个很简单,直接枚举即可。代码如下:
#include <cstdio>
+using namespace std;
+
+const int N = 3;
+
+int main()
+{
+ printf("N = %d\n", N);
+ for(int s=0, full=(1<<N)-1; s<=full; s++)
+ {
+ printf("Subset %d:", s + 1);
+ for(int i=0; i<N; i++)
+ if(s >> i & 1)
+ printf(" %d", i);
+ putchar('\n');
+ }
+ return 0;
+}
+
+- 必会:枚举子集的子集
+如果我们想枚举的子集的子集,怎么办?这是一个经典套路,常用于状压DP,写法如下:
for(int S=0; S<(1<<N); S++) // 枚举子集S
+ for(int T=S; T; T=(T-1)&S) // 枚举子集的子集T
+ {
+ // Do something...
+ printf("%d\n", t);
+ }
+
+请注意:这个算法的时间复杂度为,不是,使用此算法时请准确估算时间复杂度。
+- 扩展:枚举个元素中大小为的子集
+首先很容易想到先枚举所有的所有子集,再依次检查大小是否为。代码如下:
for(int s=0; s<(1<<n); s++)
+{
+ int cnt = __builtin_popcount(s);
+ if(cnt != K) continue;
+ // Do something...
+}
+
+这种做法虽然正确,也很易懂,但可惜效率太低,次popcount操作浪费了很多时间。我们考虑优化。《挑战程序设计竞赛》上给出了一种算法,如下:
int S = (1 << k) - 1;
+while(S < 1 << n)
+{
+ // Do something...
+ printf("%d\n", S);
+ // 移到下一个合法子集
+ int x = S & -S, y = S + x;
+ S = ((S & ~y) / x >> 1) | y;
+}
+
+这样可保证每次枚举到的都是大小为的子集,可以大大提高算法效率。
+bitset,顾名思义,即为用位运算操作的集合。
+对于元素个数,集合的任意子集都可以用一个或位整数表示出来,操作时间复杂度为。那么对于,怎么办?我们可以用多个或位无符号整数拼凑为一个位的bitset,容易发现其操作的时间复杂度为(位的二进制数可用个位无符号整数拼凑而成),其中一般为或。
C++的Standard Template Library(STL)为我们提供了<bitset>头文件,用于bitset的定义。
用法如下:
+
用法示例:
+#include <cstdio>
+#include <bitset> // 头文件
+using namespace std;
+
+int main()
+{
+ const int N = 500;
+ bitset<N> S; // 定义大小为N的bitset S,初始为全0
+ S.set(1); // 将S的第1位设为1
+ S[0] = 1; // 将S的第0位设为1,注意bitset可使用下标访问和赋值
+ S.reset(1); // 将S的第1位设为0
+ printf("S[1]: %d\n", (int)S[1]); // 输出S第2位上的值
+ printf("Count: %d\n", (int)S.count()); // S的popcount(二进制下1的个数)
+ printf("Size: %d\n", (int)S.size()); // S的二进制位数(N)
+ printf("None? %d\n", (int)S.none()); // S是否为空?
+ printf("Any? %d\n", (int)S.any()); // S是否有1?
+ bitset<N> T; // 定义一个新的bitset -- T
+ T.set(); // T置为全1
+ S.set(2), T.reset(2);
+ printf("Intersection: %d\n", (int)(S & T).count()); // 交集
+ printf("Union: %d\n", (int)(S | T).count()); // 并集
+ printf("Difference: %d\n", (int)(S ^ T).count()); // 差集
+ return 0;
+}
+
+题意和解法见https://blog.csdn.net/write_1m_lines/article/details/125582361#t15。
+本算法其实还是二进制表示子集的一种优化,不过内容较多,所以单独放了出来。
+考虑经典的八皇后问题:
+++有一个的国际象棋棋盘,要在其中摆个皇后,求有多少种不同的摆法,使得任意两个皇后之间都没有互相攻击。
+
+注:皇后的攻击范围是一个“米”字,如下图所示:
+
八皇后问题很容易求解,用一个简单的回溯就可以了。
+考虑皇后问题,即:
++有一个的国际象棋棋盘,要在其中摆个皇后,求有多少种不同的摆法,使得任意两个皇后之间都没有互相攻击。
+
此时,还是先用标准的「回溯」算法解决问题:
+#include <cstdio>
+#define maxn 20
+using namespace std;
+
+bool row[maxn], diag_left[maxn << 1], diag_right[maxn << 1];
+int ans, n;
+
+void dfs(int i)
+{
+ if(i == n)
+ {
+ ans ++;
+ return;
+ }
+ for(int j=0; j<n; j++)
+ if(!row[j] && !diag_left[i + j] && !diag_right[i - j + n])
+ {
+ row[j] = diag_left[i + j] = diag_right[i - j + n] = true;
+ dfs(i + 1);
+ row[j] = diag_left[i + j] = diag_right[i - j + n] = false;
+ }
+}
+
+int main()
+{
+ scanf("%d", &n);
+ ans = 0;
+ dfs(0);
+ printf("%d\n", ans);
+ return 0;
+}
+
+代码很移动,也不是重点,这里就不详细解释了。对于,搜索时间约为;,;,;…… 。
+明显,这样的算法效率太低,我们来考虑使用位运算优化。
+首先,我们把上面程序里的row、diag_left和diag_right换成一个int整数,赋值、取值全部改用位运算。但这样对整体的时间优化还是不大,我们要充分发挥位运算的优势——“百发百中”,即利用lowbit算法,确保每次枚举到的都是目前一步可放置的位置,减少不必要的判断。此时,我们改变diag_left和diag_right的含义,使diag_left表示左下-右上的对角线上当前一步可放置的皇后位置集合,diag_right同理。见代码:
#include <cstdio>
+using namespace std;
+
+int ans, mx;
+
+void dfs(int row, int diag_left, int diag_right)
+{
+ if(row == mx)
+ {
+ ans ++;
+ return;
+ }
+ int a = mx & ~(row | diag_left | diag_right);
+ while(a)
+ {
+ int p = a & -a; a ^= p;
+ dfs(row | p, (diag_left | p) >> 1, (diag_right | p) << 1);
+ }
+}
+
+int main()
+{
+ int n;
+ scanf("%d", &n);
+ ans = 0;
+ mx = (1 << n) - 1;
+ dfs(0, 0, 0);
+ printf("%d\n", ans);
+ return 0;
+}
+
+此时,计算皇后只需!
+习题:洛谷 P1092 [NOIP2004 提高组] 虫食算
本测试中,两种算法耗时均为在Intel i7-12700H CPU上次程序运行的最快速度。
| + | 无优化 | +位运算优化 | +速度提升 | +
|---|---|---|---|
| + | + | + | + |
| + | + | + | + |
| + | + | + | + |
| + | + | + | + |
void swap(int& a, int& b)
+{
+ a ^= b ^= a ^= b;
+}
+
+位运算交换法扩展:超快GCD
+inline int gcd(int a, int b)
+{
+ if(b) while(b ^= a ^= b ^= a %= b);
+ return a;
+}
+
+inline int average1(int x, int y)
+{
+ return (x >> 1) + (y >> 1) + (x & y & 1);
+}
+
+inline int average2(int x, int y)
+{
+ return (x & y) + ((x ^ y) >> 1);
+}
+
+inline bool ispowof2(int x)
+{
+ return x > 0 && !(x & x - 1);
+}
+
+本文详细讲解了位运算的使用和扩展。
+创作不易,各位如果觉得好的话就请给个三连,感谢大家的支持!
+]]>b7vHTS|-tp{xw|T_{ zHKK(21|XfqCnuk5m{+S(c@pi?)vLGtuE6AT3L+UM)bZ{K@1x&^G%!oMjfEF42_7oHg& zc35mh%pU4quB(vx9fKCvszrLG(8}b|W};rElY8kXNcq#I8d@BSocvRKV%~ic#b+I_ zg7-)QQ{HGTP m*ayZ+Z0RS0#a;bOLf$YR8hG+y3I>A`PK= zkY0(o1jGI~YSQIm`NG~Ev;a`7)IW<--C_NMUv&0cpyeEH)~0&;?_XyXY*T%IE^QIV z$Q+yK9Cs}GroVU%Sx(wNl*>0491pqp37OVD$u}a$Z__q9nC69ll_|RHp00isL?BpO z{F;9TP+#kjZezgTDf|uvEz+tqcJ^M7+~JY`gIkZSj&QyKVN;&UeYK*Ss&8m01gSu? zIPhu6pWQkixm7MkOLcLCi$u&H>A+jDd^OmF1_M_gn5`0p$Q!!)x0zU57yq(tcG)2} zS7Az^T_0aLP*>L9+<5l^la|nJ3(14D%DB$Z$k_M>Y8rsHg-vHA2Er)0m7{YKHr;)= zOS`%H30r=U(VXoiaXaRyyy?!|R;aF&m*aRI_FiJu&%fViV3+R6AkcG0FA{tIv(?d1 zR<`r&i$1ZlMPx&^!*xTB$%&F@dY+XSHnu!yGIu?~k%SqlcuEJdy)QNkv%R;D&WDp3 zNh0*vl1Yu+mbQ&=JV53lPyDD?tdw8B{cXWPJ&ewiAHP%K0d|no{twMJ)?42K;vbkA z{S8G| 2oW->9H~7por($#E;2k zkkp=Ih%{Dz`$i?=(!YHi|6u#*oXTy{50=3!gv0aR>#;MphPnqqf%DN)^!EmY_si$( z?5(XOXV`%? 11B^1;vCu$f*xmSXxsRSIEf7_T5C? z-uzL%z1c1Qz$6*E;*t_z9CYQNg!KOS0h&!fr 1z$&p{qc_Zn@$n@lB?T#r;@)9|Rraq}L`Cug#ox~lv8rWBP&HpODJ5knUEq$F z?(ie5dF9mswP7?W)g+4e^ t(TV c;kD`OwhN%Z?u}hG*SG_1w%QiYyKf >A2pCT+nev%R|8p+()fp#&P8}Q% z_!mX^Fw4#W5>Abqn#nILL? UvkohQC=#Z3Vayi<4&w8M&sHms6 z*V@L$zm6fH7lfpD??lY|*N?(jot=<7PvI5O?@&26%#HiX<*3_ES8Zm!+l~9)a4)`< zq2Nh|ef!FHl)2Z0&HUaKrQmg>LSX@^=Ua94eNngJl>-)Hti0Fswgu`e1!~zwIy&E+ zH%FhA=zu3DA?#<)-R9!$*%%PBNm*I;f3pCF978=lEUc`b%FDY`@8g9GSl%iNyK@cL zSXfX$)QyZlCA(^Gm&(Atq%EG<(q#+&*6*rv#nTmw%AV5q#>olrI3S5Qt<3{BO)agQ zXWeEM2Yh2hn4 66kDWO> )N8dkk69xVc zn%oN=9g?9V^+S8lDiQoa5=X5M%+L xsVxc_2%4=w7=;_^4@^_^EARDtpMasCJh@dEx z^Jv?p0m^TNDznR4&^=L*)EJeM7oVjfGO!=~9vBoC%lZ fM?`1GZ%QD)D;D&E3Gy5@VU6)Sd!=wEUTx*sNpGvEWoGb&pS zWi`Fs{t29oh*`C&s>-m* EX{SvH)uMbT+mql7Ma>d6F z5;&gg>FMd~hZ{%^t~&^_MXqAs$CqjeP*^PXpE`aWOREBF#gbQK$l ;2!he?fP8&LP#{cYN5T~7m?^oMVs)gzb zs=g0x$5eVX>`*=xdKfZKo1fAzTLh|%J7~T!qK67EU#>58gjgHPQc|1gl}}Pc$>FQy zs1JX$O~CLd{YyuQ2lU=HIFNWlmksR+)w6H{sY?8l3k&*UWjen?ln-}uz42c&-ZKY_ z1>_|S7GkiEURZ&342J`*i)m`W#J_CIN`4RY!RHMwO@}`of;Pv+X8Mk|%Yr9uz!H{g zbo&^dQ|)@zI{>$}ZU<%!b6LsBE|o7EpBT|`7&V8Ga-|c5z8A2kGB2&Jehbnn5V$`( zJCD*-fFn&ex>xFyM0J|Q4iWwGGv(VCRjc5rDopHK1ERj^oF)ABQ%lQD^X2YCR#wfD z2?sq5J&?n2(E_SiN3c>IP5~%jE^^bSXD@#Q7Q>$&_4jGf`_9fz-MTtA>|@MBM5WDx zmF@-5k3ih~19(oqjHDrur;B9=QH<6$-U;dl2y$NA1`GNXW}T~sHw= I zoSZ`g1782m*R3x@!FSM|u4>uLThCTnc~{(SfOuV3Q**j8lG}1IQEtmW1CkcC$3_km zsn)CC%TIClhvxRr!%> xcGQX`*^M}8fwl>w$ x2M>k&XHUO%JHLcLK-J5R`%jSIe@S(W)f(civpg(n1 z%-)Gg2)SKZC`uM Q0!33@tow z4K+Qr!p+A#lBk%py1E*0uQy0Oq6S(ht2PB6Wov7#wgP0Q3~(Bc-9$eq2~x_Pw)CRD zJ_D|@y}fPhzLo@6Cb86RgbSTdyIA{X|IW?gft#?fFgV|EgaH<)P~=>ioL$fnx+(dF zPv *JXV}gMonowfibTZLNA}u 0)Sd@jr1!%~dB(>hPz s>QDnq|4SRW!Apj@hXA0~i= zjPI&jB#eo;`2?}hLD05Z-xUfX$VC19D&x=1Dm9nN Qa63si@>(#H&J%lvh?(q5V$$nl5eGOjfkgDX3OY z#j^LxYUVFisBbo08VHtRFi#$Ty!xc;$?Ggb33{|rK9oQJ**iNs4wN4a9NHBDOJK%+ z58!xNGXv6a@*9%!n9h^g*jA#+>>%-Mp*Oz)_0#}iGwZ~%U;=Tx^e+w9`CEb?{)_A! z9CAn|ni0k>N558b{n%ck40(we)8J3l*e`&SM;Rp-B}cE1M`*o?8qqKdz4nSAlH08= zPjeF_OfU&@tXFa{o~dXAWw9 y{NRtO-2|ATj0o|*+29cDxR^`R zlW>w{aGT4Sn$q%nk$ioz+@CWZH$JXi`Rl5mcc?#=dnGqU3_$AM?rzsK#0wLZ@_~u< z_C8=?=@}Zz29pC8>`U<#3i|$CWFb6!O3>f6inXV7qJ_t?A4*^*mIZzLYVwF}si2?$ zvP`^Pi*y7`OHk+tebIsncp)`{k6|LMZlMF30LV)nYY+dd%Cy{Pj*xppyz&I4h>fHV zlyO#8R^R~BAa#Q4d+ae`^HxbI{6E7NmIq8f*-e~I{mnC5=V!yu(zg<^q)@YtkB{wJ zu1`RfX!Z>v^mnadBqJee!c2L}Mb-!{k%6#nw)Hy=qv37k(6q>j?lR+^j-f!2Gr=^% zXKk)~G&$lxLU6;~L1vB>^s%UjMO@qmI7a9NtX%EW-etxaw?AtuB~PYNKt&M!0rp1C z%;RN&ZXbh1Hi# gjRfk#Uh$eb>GI-w2X=Er52y znKvqTT@G1u7X#5&+$ZaOIHver*blxx!^f#pR|MIrSd*|mD+?BdRY`I3r8AHx&^=K` zTjg3KYex)-8FpBAAs%MpTnt_n99(nSnQN%Y%{5={&Rbk&&i3>lRI*^c*;8cp#v>p? zU(;xJOp1#$ Ez=`ogXq9SjGykFP$vC?wGv@*)&h05yqEW4=_$7M;70>iR{;4N26>Fac)JPFD?GaVTS zSU-nk5?H_zG&GGjx2W*UZBnX>>>CC<8=H(A)%W{9cERXWP`$>gd!YWE87WpNA5c8t zSo!H fqRH#m?9F!! z6v&9sWOx>21Eq4VFHgbHGI@RXchTJ8o2BKba mP?{y0ZGN)I#Ob zEz=>{>lM?JbW+MLV9!mJ$cumi^5KbQC`43j!BYf+x_(59tdpZo`PieXriOHj$WV{Y zZ%_ElP1ajYEk-UE@_g-EyO>lNtY==3U0RQjp=) XLDD*|BKtfGAL^A^|^c2 z4gujoT>}v$DCRVmh(9x6CZV0e_0pz!`GdFflyBFLi$R%A&CHX`S!TOAb=!M)^z$_# zGGftFdG?VLUeV;cdX!X-F7%WBG+t;oKYkEn-3HhW21WMZ;>@%0#tTd`Ni3BjgfwgJ z8a8f_wv${7qobh}J!LNvIVkj9gR_{VbQdoKh e_mRBXr~!yW$*q}beHOE zLtV2quS!qqTf0TkRn}vR4-}_5W1^~LQ$rQew@8K+Y^|({^7Dhp)mO0FML!v!k=j?S zo?e1NLxYbGy0XOP5Pgj3@`0Q8&E-OaAqRt#v#aaPu4{{V mKuZ&GwwFAuiyinp*m(}ee|8wpo;_cFM2xr zCY}Fvc&8Z@lw%Iizy4G7kFsxOmmFfVk<(JSTA6BJEGw+#$Q34>t!QH)1h7PPi%#aQi+&S=}eXhEFw4Xie zxCocab OskX}Q}wbJRn}g;*`>XcJ^{J#iM%+Ev?(Lf)PHDK0C! zyKBXLK+%KEuj`*{P&oE_x~jOUDlsfgSBp9^qHuB>GB&*y9v})Yc%9BMXSvZaGly`K z;DvyJIdlX{AAltfNOm>U*S9ZmeZycB$J%)oeASFHA3l0UBH>ORN(&h@0NpbL^(!ma zz4$uXB@>v>ab-bFk_?j1lYe1p38Ah-(S?Z#2%zBqgaZZXSZFEb<(#~{W48Rc6(pP+ z=h5mY%u~{_TWNBl)hvG92<-m&@sOsOsW89Qx9$r>c_h)1H-gRu$$*efQi5AO(+V4h znb{nte8kxWAihoY1&CEk4rH0eZT&!TKYd~?nfT#Ina3Aq7|wcd(9iDOZ;9~tMO-gE z(yNC c47Z%? z;Z+$H;;R9NPEAdnfqiv)dI|xF;11JF)xr_0SX!C;WeRVab5KYyMJZQnrTTW8-kts& zA6N0XV11#LfS;_u@Z!Y_h@<4*)l*qbtzTSuC*5I;Dj9xglC}O;cn%T MPmly+Ay3HV0KqT-p%BV%v#E|B3h>9@5P=~460$*+)kxFn zya9O2X=#|hqPYr`^8H)hpDccql~K6l?t0U4+CuKj8ff5r%TJ6vU d0y&uoa^;;8qE zxqNJRa-7Z;P{E0Qfnstg6^JQI;kIRV^`5{oe#9yf-c @|KCX< z4mI2WT7?^Q*_};`i>t62`3T<+y?<0vRRvrb;EZR~MO|#lWitBt1oE$sMd-~>Ubc}( zu~|W%zLBeuzLmb|2`$fDQ(I+=bxIDt)WEtsu5>|fS?P}1o2l7@eGa21uUxpuz7KZj z4uS^Uv1Il7HH7} ~;SPG^Awsc*7UOwJX6!BE2H%LZKw6 z$lR_J{^Da^9)!t9>tvXJK119%QGN7q!x9!o2n1vh9&V)u!Z3(imrp9=CgXQO(40I9 zt&8G473^sE<0hl>vCU!H!1Z%=78#F61Z0J(4&j8&)1^$LCJcl^j?3TM0?_TX@O3#- zItZf9z!{qYf&bIflEu_mm=*GC!TO-hj&)y|fv{RXie~?#Y{bvc&qW}T4jLYW$wNpe zx$t`&Tp=U$-}nY(u5=-Dcz*cetm1*cY?lzT)x(=YMH4Zj4txw0$kn-=?Jq5fxT=vj z(0#vwAAxrw;V|s!OsJ3r2W+{ZUs;CU`jw`_q=R6oP6>ekc>2(4^g)1q_G}U;IApX& zfhB<8Ky}AmX7PdFpboe} 0@O$ zPT;>HxZ1YuTo1LSdA}WdZf*{^2SoHCepcsk{Hc4zuM+DM&Cn4B8}(z4zp%NBieV%p z9kj*wk6!IUfDSH1cZ9MV^U_I@LO$i5=@N>-J$CX!yfe4c1qFuDi3!Fqdp6-s=8xvU z#hoYak&_eZa|149>00?t= MFlJP+@8Yd=}j z>mdGnCrXuSdh5eHH~eDK29DSp$h4VhC;)*flIBPW=pSBP;>nM~lc#h)yr`&fb8nj8 z-L=#2W4d @5QHhVa$w<#PxwtTz$Homx(sDHRSd*r9ziJN+b`Qf zwe0z(cdl>WKC1Th&4}w;OBKKhYF{~^1REQK3854QJ-}ymcc2PkD+?(by3x~7JnN&MJ6eSAiYT#H%XKn F5vS0w< z0=XaJ!p#nn#99yb_?VnJy3cm@@2TxP5Rx>Io_-Kf^{Q5J!?;VAj~u3DL|p#3-CUnS zHW6em2$E}?`ykwoRruFMNU;P-f$Cmo+7Vos ?t@=kujc^(*d3=Mcxf N}4Wbm8F4N*gyWHy2 zr%$0f3;8g*9S|q9l}-> zx)9-EB|Ni3>L<`)`#{QOE87TnKtbP%?Dya*w5Rv;f{+o*&mJ2>9vPU;QwoxFQ&26| zwyDs)-T7c|bLv+Wefh!#QZX40;K&RD+`qMF{HT18gC7J<9)igw6Q3rxp{Era@R;LZ ziALCe`hntTTYnPx)~4#Iy(kW~dFLvsfi8`4kPhw-1nQBd$U-pU{M!~mW`6hXoxK)? zI+>s@rBHPGqgOvKj<-j;sqtSgir}&pY1h&H<<(f`ew6s)KoiA=CVCv(A(Ef*t`3d* zH+mP@0A(Tas2>(Q#DtY`jXx6!S}~m%mLh%&axgP9|Ic#73(2jhIDjn;Ln$`ZAWs+p zY3O_*vdZ<9)4XB|tZ~p~R5OJkU!tL@`4t2v$UWG83w3)$LZ5@*rCt$I{iR^p1wr^` z-@s4gj(7Bl!9|i$IVT%iX>oB5lSP7vLO)T~I+! BqCF5;?W^gSnj=4`deebM4MB%vQXkH>8*s z;#HU+ej(ozRmDupM0Q>9mVrD9Fmb729gnFV0j6i6-w(nxmD?))>({1J9fYjGR$PVY zPL7nJ^$t875MU}QxJe?w8(#nY8}vd5TUP!O<>Fd&5H#@x<`vQb6R=hKWstZhkFu;3 z5)ha)t5D~8!$uNNL~xN{$HMaE15IW`Cvp$WMMbEBb_7=h{^igR#0mf&Ln{Av&=00% z=Eufz$2P5$wM%*6{c}fPh~eKgWPBf%Hy#d4-zhV0Y0k?FJbCZ?E|N!a*mc=O9Al$@ zSS;3wElz=p5TA@bXF?0(D=IOTtcFIM#bLd~9rOpUpb~RDe25JU^r B&GcHlui;;V9}hyT zJB-??v%n)o@ c>uiq~YpE6zq1NyFV=yB&Dn)WDv`f(V{&@j$kT4k&4yo4xSvVRb7$}WKFzW;3 zGP^rFpi@BzDfC*MJlR3pHdPv%Say#J>#E%_kSs(eELT!flH|q9m$%az*B8f7!*q>f z=Y8#^f~651g)?D~3OaA7ySml@8n&- AN4UGrPLvu!iu^$)i~L%M5{5V> zVLTS(Qb-;VEY?8j0TERo$($E0FhY>{T~}(SnfqZc^U=`}t@q}kEYgAy#{Is1W|%%s zP9`DKchja}FXbUbW!pUr3Jw0zjbbo4a?tD4(FpMYu#BOv0VajX 0{&2+p<)dI# zrFsEo7Ud6G^m#Jo;5b-Tp7$d4$^73ez`x5q`O~q;NSwTflBbrA-ezTRH*i&;=zw^8 z7KBY2+XE*p#}QvVs063Mh+&@GuUA=qyI6* Cp>zplDqn4HTqU4 $psWXM@s$$G{&4ER~g&VUj@) ztXoh_U@ZB`6LwbCA45Zc%3(Sep7zvRTJk7zoM4!90hCc%%2qgr=??=7P}356Ex5=O zq`RWqZYL_b#i8s$rc~#%A?DB*hUPV_&NPL$q!;0YX9@f7NsvCtEib>D8s)$j&a{JE z|Lqop8oJ(}re$c@PX^MAdRdYZV+J?=A*ewBrl2u_M ~n-LbyH@#7wHq@2*;?gG%a+XYX|`?O)<)*OW?nYG>oW(o=lFh&Pq 8CMFL(_DN03PN33fs$2FZTM*zp8KMte{w?bZ^js5E@ zr>~`T1cAOmvx-uKnqBzaXV7=jRKS5&k9EsR>05*G-LdESns7eKbST1P-or4}`}ZCm z9&9AHut#5Cp9cTS%+7EuOf7)i?Yc9)<}3^<&yob|!-wz?|2KK 7EeO)hY#RoQ|W%SP=xuCkzDzbk`ll*!6eiUb`O(&I0)W4 zEHDr@G4Y~i2F$29cpxz2Y*0SQ+XVu^?DW(YZnaKr_`$4a5$2aJJ<(d$IE6^LmDbi) zNLbt!yg8d{I$N~+@=OVOWj_JbX%<+tubChCzkoL81b7Ali741~+Jp{nSG){NQ_ 7!$2C$=auRnv?AXrg(_W8@Ec%Yp=##VpFfGc7Irl=d#C2M zo}PvA_4W1s{{G mc+ zouoY3Ljv~mq5#=+bohZ;EiwBy2TU;IW2gXro%r;WSv3O#Eud)Nx7^-C;S-u5VZ1D! zZ(7Brr7~aCovsU(K#jz=@|GqBOK0g9wXm~7$pmELVK8yIkIRzcH{uI&-rHevvob@` zN9fHY97f`y;l{Ntus4LFl>wkHX~29+qD|^TD&uPVJ+Y;;^nMFs?|aLzahIVtbNz 1_1pmWp}a!1sQk3u^r z2Jij5!2P&5FYkK+BU;yxU4&LKVio8b8@L6vg34oq7H0H^ZuTScFPdT6dvcqUU7ruq zAk)+OY_)ABvBGJQH)x~rKQOnts`OvOX#n&1z7|;u3E(W0(o#=R*FEs%0bEL}eq7kg z7O;m;6rkE^QGK9;Pfmk;9`4^pL&)BP(m9PYGz?=%SCDR`!^?xW;V?Xdwkvrg(}+bv zu41h8SMf`8&)25vh@cG euDWqbHg8E773>@ZKuQf7!6UZQ#ATUS?Vfak_H$Hq`-2OYuEq#`Gm`h|dGP9b3D zxtX8xqp$A?QvyTtgN*%DPtldApD) >uC-`IqBcpjDHPH&- z_p`HxWK)w#bYcAanIYP&`6l#&zhG={WP}!HIH5+5bt8YU+f^R~kW>;H+V9wn_u~}u zYHCupqCT|gJbNaszU?fW&>PqSc?pMCm|lZKuKP%do}A-DFy#s#0^pBhk)~qFpI!q4 zbD$@{%nxK`D8)R_;NCG}9uQ*{QH@yrh1fp?FE{`EnXP~O3L*ic>|G1^Sog>cJs%E( zz~2I&9Rbqaz7%{#s}nMjjp)w=*$+Du+~IxZ&ZZ6&-Or7fdf7frCN8Z!2M+0C@z{ z2UCLRihnw>%XCCQe*~=L4Z?d`S{hf`k~G2Z 5!Oy&5% z_@Xv~!sQ4@axgk|pnjP*82y>5>*e0Z4 V!$03CN}as=!9Wo zuxG)MpWJQ}&RH$wj@%d|_}6E=*G9sD&bDiKm+DE5x_Wg~n@R1>ze}*T-n@C!a&_1T zGi-m}N42pe{eX|LfM8e+h#(Nra*)LC4y!!#6f62g6%+CqwU-QXakm3NYe~;QKGztH zY(Q}bTmG=zYv+}6GFuLdv&wJ^=h7$ZppSi$PVc A(niZc1kQKN>(%vC}^Qs!v`nc}? z=MYF)3bl*`$pS!H`ap7awta CGZk(OhDGMJ;@vwq|S!1(r~8)l}bbwvpv@FeB#zL67R z 7lU~+I53>e zXUm|`%MXfkeE!Ce;e7biiX#h?A*`l@xE|4-TEWi2v9J@jaPT>zodOimVE7yhfI{Ja zirQ|h=KDr;=$t%2;T_>`XG-W@Ke~q#tj>ah9lIjfWORoKM(#|wAl(=o9DK4n7eVvn z#S6yOrO-e33FyUs9sD1fzB{hw{{6qbw3pM~Q+sRgC8>mvHbNRCNqcCWb|giU$|?#` z(b6JKDoT?|p-G5-&vSo%-~PBCkDJsv@AH0Nuj_iQYvGjI c6jtAkE{7!ja#@o@w$
5KsvR*p;It@A zZ6l>6=WZjigIgdOp%u{YJYT=(w;KiLeXGBbG=I--Rwsh@^GVGLpRmmryiC&tOwOTC zwz=nO<%Q^7LBI$p!C;|9x{2+Xp844$E75WXZ6_!$x1J*2+rMM~DGU{iG#HJ3po+c! z`U8hNDEa)p?>~X6!js)3Lezmv$~LA+$+L|yrpfTvlM*8>; {LR#eO@S6_wtBR!5 zpdU_h8c?tzGT_E6!a537Ch`E$2UYk7_1%l9j|)no@BQ!ruAGmsdi=qSx*=Jcosq%D z9FN{$bG >hBjD6i?JK#7|I57VWC_OqIg1OtS zUCFj(G1B_*ft;#rfMc}knaQ`JFgdEB;py$nCbjSAZnk{?HobbjrrmmilNH?qA9$cHiQ;n7{F8jw&8LR5U<0PRJ=@tNrcNj8Ej)Q6zT zF*pFgU0qp$nM@*9B0t)he(lZ7on8XJ+=^4^_B5Rq4!KU_CO(duJRY|5HyZfcq4-@~ zTtui$UUyr7vlhRvpC6>l9kmHBHP;0^8(R#_E{9&cSl2U4$%5M+6`lZrunOD&2;G?J zf GO+OdBw@L3*1xOnJkpX*t-6s7!ijNC+jBPj)NQpBGR<2Rte z`x5Bb8x<@24*)Xhz;d{(M5h0|Ncl$i-n|2`{rEjP#DkbCM)@eTz=$B)k7ykn+Ry}H z_)eX!IV7)XO?f?lVwyhAxc24)r$x*_Fd;((b{cx*Q>UsA-9zVz7>UYCKV*qWNF2rB zLQ=RZDq;knTQwFaxi&MYsXN)7C%oi+=^%yotj?1U0*R*h$%o46jvQ}E(_LPr;N0yV zuxD}UljrFpEz=4|5vX<)?;cA0N604I;G2m)`wJ7>Z63LR M`-0qM#{Z#lybvX5_SkRrDFaV}lG zC3pXL^fRwhR549}j_bzm4=r#6?P_gO{;o{kJh_D46bBn+6dA?iCb!<%Rn!hQK)`!+ zmrX%>s62R(N)=<&8@#wvm6w%0Ms&Bp7W`rW&y0-7brZDi4$Rk#_&jjB`yXJ)b)IH^ zAoyA^)n?U7=H$;Gl61sT1-kRF2a^}|az4q-ZZs6GirD&xu44G>50W7I>Ff;?xow|m zQzB&@l@sG~c7HmNJ+&k7lGT*NiS#9{O0|!K%e-=KQ_aJMl#bRBEeBb5#p*=8&^Jl( z*9naGRs$D SLDVyTPnUb)GmXf7Ks!AD{%e8xu_g6Lld^#`j_$ z4^%jAS9BSi4;;R$L|{Crb`5iW8zG57d^xN?_M2KUgDKTAe{0J@@nwF)Gyk&0y`mGy z*shu$RU()sl^YiYgYLhNiRu0Oqze)?PwZ`m-oAZw`h&*3{dFK9G525V{=Bxf29=$- zW8r6VF*qFF*PT~%FiMa60yQ-ED^k1y{I{8JcL#x`%Vhpw>XFY8nLOJ jMu$J4Gq_UF$a zN;OiScG;-pCeNN*gG&`+Y~rIvp;1wonV-;kAm0skwwt$ZskHfX%)L=+gFO@gF!Itm zy^KAMd3sI*;<$93+s?m|6iTL7s`lm2q>kBTA)ganN^IA VH|k={!V@DUbsT3n@Crq)cfd^xdT5KJoSXdI-wN74;{U28 z7DDSgS*j@&wKEmZFFdc1m-Dyp+rQtVuh~#U NhF{_x|u z^^mT8jhZ65ny23fE>D@QU2hbt^`A6G@1n+gGqVs#PV&p>XF{W2@Is->fwK$&UY19c z@|7>;+nJgoM(*Bc;hj5uVwqi>kpyRY+F$MCA#qjJ)#Y}f0;tFrDqn=Jf7$2M!5N9d zrhPs2_xtxgkD*0EUYcl&kwVV&JPg!FO8P)T!y?6X0QoBoF5lr&fZt{(Cr$4ky0?ZJ zjoZW8m5hEo%)8u(mfc|MSI?g7+7KQfnWO;H0XXs@r1QGc_+eypwElR{4U8uw1Ad;e zCk=t(VAOivp__eFqSSm}bE;gq2`)#D`~}$n%Qq+mc*DF+uToXrpS&7%*V4+WvxD?y z_&_HCS~VPZz_wVaUj`z|36{^y;7hy@=_HRF7Gz`9M{Y!pTvjKJro9dhwZZm%-goGC zp6FnRq>24?z|L-ICTq|0f4>)yvj$@bDXXDQnu(BRC3_ixFrP7>;dR0jCVPWd$gk;B zP+d_6
%U(W-dj+Vzn^NHlROBx`9stOG>I~8Tl^Zvh_tA~fEt#%`H1v6c z d>plQ5eT89+?mn03Q6LQKE@uy#u{?T3j6+TBZ+Xi}%~ z0D7q$#DoUjjiapDdG!j%_ZS?;#+W#E5EzR~L>+z!{}iSd%dk9h YXhubfuA_lF@AxF<4J%-0TKX(9M4LNqz)6debSuL4^7@t^{k@X_IQAy z_9!MRypOrCfa;&$-WkS{*ZI3#wl=#+mm#sUc(<(}t=xrLey^{PFFWKV!vqlD(ua@4 z+1W=U&x)pbO =+DCAH5No>x@N1H`B`+^E4l zu=S<#pR9qw3;ZEe>HsKF#KJk2cr;P35FNMGGj`tG;@tI9-&M_2@qwxKm;d}IU2Li5 zIdD3vDUwM+Sz$ LB%dy5ckZtDUmQ+7ouusMLriR1`zS}AX=l=q z!*e1e>)7W|gBy$I1;i&KGdT1A#;9AC lSD%XGsd{x^jzdk2?}CkFuAG61 zDbBrVFOX~?W^PJrxQH8*@yNT9*!2&vki$5?{=p(U{P71LjW}cSPK(PzPpwnTAp+cH zWR^=V$g6!Wnm>}k{YS8FdF_-rl~F=!>izO^t? yyJxzLt9nH7_nL`E_0|8p=Z_{q(HV%Kes>7E-Sj^z}U&ksBdjD!zx#>NM^&sh^xe zc|>kuZthb6E-*3VqwqjI=5{mkfK`AN^_^@W_w%fVY|~HiHk?)Moe+B{bM6g#kE8iP z7T{Uee VA%FJZv;D{=;L9tIrh@7Flu+_G-v;^t`Xml1d6d+H);vt_CP7d&U zpmJQEhdwPUlLDGSPIK^XK1((|6O#ZhZ*;lX%TKGnczJo#jiCO;XFx*j6x^u%evG}q zW&axV2X9?~nRYS8yaej$gtWAH2Gh{~k&%P0u4(?vU0iaztL{v!;Is+G01jmM?fAG5 z9TmbuU%h$-Y#uE8zDdfc$btFL^|uH1 >#uqvRPL1;&IN7>mYyD=R8~y{!!2=jsZVDA^HicJ}_^VY+iwP#bJ) zY@nUz^cV6HVmmjjQgi4lAgC>bM6xh4LhuH682G$EZ4+bT^!G3sp-TcNSNLY`+qe4_ z6-a2ZyI@zL@ yKl)rY<|#;kKOd@0xktl39tjP0?hTUO@8L4`k;^B44nyl*&r( zvi0T6%$2Wx5jud5gM+o? BciNx3fnu`=aRY2N;poF2RL)6^D!T>2F2 etnP*C8|yD_{=$6=BZ_=_uyuAgG~0ZK=uQlDC#S zri=d~FE^1aifMs{s ?5rz~ae(h>2Ie(RZ(@L0?Zre5!PHsWL8=09Dei_Ry1&P7mo;;M) zO_WFyz1>6H%Q`bVtLow(uelH7Q(s@yuoY`8v`anSYN9MrUVjl60lc$+XefSkJzHk) z6$F *g)iHzaDRsSP5Ci;RiJKQ=Dza(1?zn@RgF#Ni%0)}^&CNjeF)56n_Z%Q*P6 zIyvd63|VO@<4ixn?~%s8lftDLCmb+TIbawPDfDk!Ew41|u?45x@FG6j(;k|?ArcuM zEsT0cQf$Eznqc`rHBzlaXv|FsxRwI)(OzoEyi}Hmwb=Ml>OqltD>POECtQcwcDR<& zw!?--YJU_8dXAHH=&I}!Fqq!I&zvn>E>I|sypi$;57;&zPK*ne?5^H9IgU>SvkDUA z{{DFKV(ihW1J2IQC_cu<(a13-Fea3ly13jbZ6I>+wYIg1rknS^A*_ZpXkWiyRb}@d z>*Odz@PvJ?frR}%e7F>TH8&t)box(}*t^qoO1AXI+bV}3*T$7B8<;QL&=c(G?X`m( z=cR|yV)oUmbo)iVFq0%-cPgJJC5qsk^!4?P|9W%&irm4>t5*r>#MzdoP0lBbSTV?3 z-#qE-i$d?19Np?*PTNEOo6B~4e<`N5R=Jm2&E#8uVXJie#429h8NH7oe~O>$fHNWV zKt_w}O_ertE ^8CvLm^Cld~rtpzkBheAjsN?K(^7mbf>;li7qpz=~?q=D>Ew_Op z$0Z;Dn= BF)c4*7E~wTBxJ;@#=HB#fdc^A9Ii9R zi=p`eko-?0c8jD+MUY9w $@9Q@_8V7* z7UEu_m4l)~>z3);ZGH^pWY8~Cjo@`y7BqiRu??eh CF=?LmCcqeB5!flUdoiw;Km$6jx|+2!|< zJSe9!LauR5E-J|QH?zkjNiE&zYFXz0(*jTu+*8ac57;T>ApT11E&Zdva1MYov{zxZ z606;#6s9GVE>SlILdmGe-qO-CG*lOY2@x3~vFUOeqh p70eY zo_V<5c_CbM_7;ZRbK09V9bu6oXSDc%SCp2a#}5tNM12c*fqL1fE3AoW zNW@3{m_v_tkKTk94`2H2HxUYxU9W!+Q0q^VPz4Y;6XKJscXmDVA)i`gVMh0zX)r ziM2;~5B6T(HKyP&0&5qnIx;d94 Vp#kXS4k=slNzb7B#z#0UYtm@CR?mGb^7t zGVF}@u6hI?U%iW>Q%U3_VFT7-oB9HN*>XzB-0!mfKRnMg63Sd9WZ2Evxa>#1_B3zs zZgYrLliNLRCSdl4CzHxbtIKy$#zGo%u}FcEK*BF38Z;>gRmS*^CK+imh$w*jEiECz z3E4}~(@alI%~6g>hmc7I+`g$$W7Aw<&1*OFT78L*&|c81KQP`uyY~%+3|VLfCmqw6 zz(fi}JjeX~(|sN`9;Z`vDG~LNeEN6zFjP}=U02iRmV+0A>Rx?n9x<^K-e+ IeZcvFz-BPAkIgk$8{I~TepUJ-TTCQ=k1rGn 2#f@9G(&BOZ5 zk*f3p)`tB@w0;Mb8kFj7@#ZP6A1p-WrxHJX+V#)s_xCpqEidsMtGC)+uWXPQXa8n$ za?)^8sg3NS`89i1fUH#~-`<9`a&MV!5x3Orz#KX}=y2b@e-F#F@O@C}%8oLl2Kj^< zZfJvXHNb@wg(n{;YmDh=KQ8N=JDA7)@(l91@!&Oa@tfV-pFFKU#XtJ%U#iQ7)8d7Y z5bZZSo`@jjB~Vhxeq8Kv_it`#0hAFo)4?`#h J1*100_86q!_+wwvT@nd%vbU?2OK`cG?utNq&j08 zaW?s|n#Zc&UlyM4xnlK^je7$+zKq$Vg`Ul!pcdK48eD%PPT4_yL(*t|1>I%@I@2mP zX+npWirQJ2w}9*y{9Qbb&L63tg@t5J{7*jIdwUYM?L1Njp}pUw=k6AeuH;2T@VuyW zv^S&>Pse=8ya6t`?^}ACEg-P0YrfG#ROS& ;`h*=fso=i~w^a+3nhQe#woky6Ln-LxWquy{WR4$a^+J()fpWa4<$E`-xJAib$9I zguem}rXTspMyc;xDC8WL;}(v6UR)~TU#OMeQeR&W?XiNa>=zh{c?pDsgapG&6fB~e znm-YMe@*46wTsIQ;(0cJ*_h{<2M-3-{r&o69Q~!iug*Qa97!~i;#y?6SR_%t9WgoE z8%82aVEe=6=H}u8a11W{Wm}s^v7*D@jl)_?<@U7gQv+;Lg;7*xaSlhbKN;`hnEv86 zrF+-Pg`uG5Jf<&$zw>)gK+sWun `T 9~pD0v@tL+g#TMDAcbZaR*BLe0TWg^;PLngWKt+|Nl6S^!cA^U5GIR= zkhdO6S8#ma=VMy*&{+ZMG-&5G0f0|7w70h(KYA3LYZx{n0OImqJ0Q&&*(AEWqd_dd zF(_>GNltW7`Nb2An~D;Rc!|&{LPSNDjqLx+SFT|8qn|97mJw8HgT8hD{^tSWy zUBsnKPfv%39z_oK6_?-?`JMYSSz@O4rUxB3Uy_nMxwsl2LBMOqC=SZq OF zFaZ1oMrjYp>*L>tPCioFJHs~iTfiaR>Z!$Gx!WhZFhC`CmUB6hfs ymx`9jCsdj z3$#DNVU_1UEUZVbFjvjsbtBLQab3t9eo jvkIwb zGA@@p%G$I7)hUw|T|Q9!p#q)>(Au|d(LBUWdH(P3bDTRn9jzBOTm#~@uBp7aHyu<5 zkpf_Zm%%Vgf*$T4ID433Dk!HB=6(?oOdvpR5kunkWaotyFIXruTU_m#Sua>I^7v@! z07{U>lAK~iS}kj`_mNW|mAAm9<2N?aQNQA6V~c9kVWkt7l!RUR6<`)Ur21pZS2Cev zqKC})2-UFkl&mNFFfp2APF+*;$>Ya2v;rt>#LBz7KCM72D*UDQ)_yxXvIL1JFY46T zzwbY6qowM;L{yIK2gL{7ao3fEZI(A~D*V+9kRf<_SI{2ctwey0Iehl|d8BGF=a;OF zu~K>+#KSR@FKB Pe$e5z<2?+~WYIeu z9sz;IQ7Pd@xsnZR9)ZU!l2a51@R5L?-hbCA>bdmbE` -@y>g)=xsHzu!_J@QvJTo>Jy zbdP4&uY}+4UNrvJVYMh5*qy6F{rWF!2Lk%Vk*EsEC!ih|}ZH!~B_78Is35x$H#@ z`iDf@&H5{q_Oe`|tqoEN5W9T+%9Ts8dm{&G$EE}v{HfJrBbkCufM3V#3H;EzD&T#8 zM3uKez~kN?vZ0pQRX}xy3FbN4(n3b`F4ERnoq=GtqBObUV`FD#W(s-8K7VUX Wqt!N(yTOgLwz)Dnbke^g`4O421ZL9e+jgmWV68 dpLk`)yus!LfR`0vJ1R20CtwTCQIo%aLWabMd&py_|jW&h&w-m>4ecLJ39& za!T^Y6n70h-*r-@CWxL(e1aTE4GoP ?i2)~R5L%#14&aTa8VfNTbN=7h*o5GL}!mn%?mvE z9@a73`RPogAu6A-aWla32pG57bXWq5vnwgL(-Q?K&OIZ9s&}0crCpkIJ$cvcVF*ra zXffT0 zNq^%Uif|d<&qeWqu%4rk)V=gbOh_odcW>-PL cH)yTi zF3z9)Ur3D3b=w*VQA;>UE`l{+7SM(`7%6 *vKkEA~bT4-0rcOb8212P}I?srki+xO?BeP|t_zL)d%BuoX?`gR_zf>YF=gpoOA zVnXRj9P~zOwY20P5wQi&4X8*a4N)~dWc^>ZR);DO-gXc48@QH*q9T$?D?JrU=3@p9 z4fK7Nmzilp3=-9P*O%T$elyg&fSj$NN>?c|4W*zo{omdoc!)n^9qum+`HJP9>bh;G z!sYmOrlYZJZf>sdd}?ip-#3TC*vQ%J{QP=wP6#^4lG>xq&)hZ)uN`FkuECRW;GStm zUv8+bJS?R%joQvdcye*WiHry$1sQx0c7d*U1ryxAOOf3DjzfiwB!|;OT(80LabzrP zJ;&J-q$4g)MRlN%p|Nu#epT^ivXA2HV8}n=*H-Zge^EeqJ0c#kztnDG8JP`lPo}j) zTLg-zVrW5znepTB=~v=^8Ic`TR#pa)-i+xLT!_`#p7@2l>})D_ Mr3CuQGN23y{{)(gsxXL78L<&f0RdLf)%Z=!{T2VG$Yg;Xrr zN_ys2ScCjcJLUs?IPq)Hkpu{t9dAy#?lL|nQKA}^g1suEH7E8W>(Hlg{JTNmTra%S zTfc{1z`NHwM|M7{NOrM}iYrWddf?rJHMX}weY1(b{s#rC0u&f6w6``v7eVB$D (t?QXv<)*EQBftfav}EL^agi-Uu*_l%d<3}Tvek(j^J zk6?=>A0QG)$qr~s eah;f%g<;WH0_Mi-aV zjv15^483T+5KL|7?99(4;w}=$m%-v_FRYdE(&HCt$ akH^C5jf1) zVm%cb*80(P;+aG6yKR{X!!@TtyVr(4@~#c~-XWU4;UZ&MB7;B{L`>7L)(P2z6He3_ z1N)8g)7wewiq;yHO%>F#eC|f4-KM9B!My&mP*_VJ pQYzd~N-_ZO{n^ ^EEA&*2e}+&_c=KYLvxql5PBwBG-LI> zn`a*v(T%G!0h)$dz755hlxB{M0iB|s6F~IPs2K*35ojYdy`>>N#O-58-kgJF8HCWb zuU>9 qI0FXvkd{kK{sR>3E4TZ$uW}vB3YU1)CR)%) z{7td%75RC-jPyUZ6yt<>4o0fz`E!Dl*==q8y8!BR6cN%pv0;3-b1yXa|ghrP~98 zJ5 q~m$U)U^1-IgMLh8O`+tr~3?0{@EBNNz( z@Qe5FLq2tJ@n&MHAjl#*gLsDO>@xz_c+b%qv3iretsNJeM|_Wa^@uGLk1$PpD-1rs zdYNU+1;yK$*mR=z86O<)DlVh>Bw }}7K6cfP@U!k;+1(ea#Kw1hz@bzB zL+0v{R5#+SMv9O2Qi*!-X?@5QPCazlyDcpnr-cH32dJd${rCeN!e4}%!{zjrv|$SW z=qU;(OQzFHHy#k(5OD@-#P$fq4IpnAYem2!efWBd(pqMqGMtc|Xk^8DE3a70P|GX- ziTfGuDTm^~{)KhB3s;v91Eu@DP)T`U+Fwg4gyNO&6R;2-jm=9-N} `& zRz2nHuWp)cPh11C$o1oNS?MA}S!6FM(>%!!jp}sPx=Ut@pM(C rGTDQlSW~LnjxPnR9Kt?;xNyx>a(s|@FqT-*L;xu>` zp|fzMI5_m9F?s>dH1MjWYP#r14SSgt`kqUyeG@@qE1K6Lb_aYveW5 VXy{hpp~*x~$4 zP%gvs%sk~tN~X=14Y*cFfz+_sAwwM$T*TJTh!T0uTvZ6mv!x#^u+kyI%Ag=bGnaWN z_WpzJ)PecfMn)Ev7myOdRIQ<=#}7qka>WbJ5ZAdK`=vRrYDgz}pc*+iu)7%!>u@Ty z+2l`*;9r=WYJ6&De@)rEv-*Sfag~p|610`-b$I{Un$N#8ShD3#l@YNrwSSGZncaTj ztJrBViOLC>A5tbNvV#X@Rf>2hFYS`dilU=-hqmz>(lrcIWAvG*j~ln&*!$UMYK?Q} z1Iv=ad7 zq~F1xN$| zlr=fIIdfeWvC!Gyza#q}H#@dyJ*%sOF2pGwKrlNydzIf>C!S*Qp6vTAZU0`WiQ{bt z_D;ql#CRRq>qwRZorEVE24_q#X$Jo`P|LwR`e^W-eA=IKMb-@|lwHB2(S 3J+)8e4Ql+Lo@(3BBFPwe@XL;MjRc@ zxwRm2N^bpYQD)%_GNYJX6z>Gr8mJ^T1q*|U^QF}fAoE4-N_AqX(Di)v$J^wSM5BtA zwNYiE7ri+C=OaXCC=F+h#2(F2py(3$+#2qFiIB=05-BskyTCBfbnZJqVg!i;Z8BoR zM2nUc;?V{WGy3}BFO#@zD6%%c f zuaSPgq&&^7S6R`=shE?Z87C65y2TAe-6t$}B4d)m(;2Bay}c8G+B}N)vCZaiksUt$ zH&>O#fDOc`uyzptbX$LtCon=Diw#@QpBR3xb-sV~>Q$_iD8i+d&OKqde)!M^6}yl1 z;|F{V-Y_6OaDqr|V7}GPA>D*(<1T$hnrjSMNpKKM`OwJW9~rSqQ~ynQ(kSY8+UO)L z-$|8RpoLKfy|fPFNP{-EP0* LL#c4CyNLDszw;Gz^__xO^8fMM8Eqz! z)_y_@l|FEcSfubCwn`_bj$q53x0>6Ut7AJsAdm^rJ}ZR0lVP4|<$^?3)wBvTXL|%W zU+bKwQ8S&{h~&&F+YXAaky6OG!)#WEonE-8*kRiyqNY^~bQ{b!F^w%bHI=kYW9aTd zj_uoNRqPm@$PZ;%y;1%;BY%r#Gn1{mRtZZF(I%BvvQDbDVVg(>Kq|Q45j_dvF*qe` z`o@@ApPlW?D;ocCA8u)R^&&Bcl0M l0+>xm1eGI|D&r`4AUMj zycrpj)NWWF2>=pT#5fm&2JZFSGZUZxPYZz6CF07;Y~4Fb)hi+uxNIePENL$qe^HVK zxo!DA@TLWAPYWW%uv-}V8Ijo2{{D6)eG?3QjX(&(4iBFX^}|?sbsXl}XZt2hM}NK?!1iQW!SG zW#JnwLI_gVLdNy-39 =s?#0sVdco;wnZ~tIFQOf;B=Jw?%dC+tG287m4RZXv zd5PJC_W|edI9b_MHmW8c4a}-2WSXL)&Av~hBqZ*^D+L=f+26g}qt+_Yf|nlh9q(5b zB&gC4bG6&bc6x wVx-J|d!#5KPj^qB1}VULh=;zvR92 zM0yBDLe{myy%fKy#{Ygh56>|o)~H@eop1?o?Ed_2d%e=8d|O@UuTMF{hpC>SvcL_3 zBm+Wu#Hg>P4_Uuu@bCS19zB14LmBe8=Vv~3kswe69jUb#>6Z%>(0gsmWr|wWv)bWc zL(v}{M$s*x&-7+m^ZnUdH-(4T$_w3;oc@ {u{S5Y}y>5#R*37%Zy z{-@?1ALZY%-^nS#L3R4groo*W;T4yo`Bh}c* =l3F{~bkPIME1;b0`W z`Z9hymj1O7jve&4t8pmKG_}oyyK!NilwyS@rVhOlZY_O4Oo*ug!K^D2{7SUFT* 2u|L^_k6ylv8TFRSwmBJJoJ3@JYR8pp#t`%p@l%C|3*Hrw;;y#+tr+FpKqn z^D@Zwdic18M)=%`j%O5R;vcm%t~_*(RVD}u!XX89&^H{ntlXq75(f)VLCh do}8;_*a(k00lX96ijbN}s&Yb3ko|FXU_D9z*zt_kBUA^#Qm+LWo5zxtA}S zg&lEK+{{{b5rS7OP#X&12QUJG<>+Rt-L}yQ<|8*0E8dYQKix8tzDnPqbp#7%NWOd6 zwvv uqF=PctceBr1=? Dj0z@e{BW$4)Jn{YkmeGDlkbUO*-Mm!y*>=!^F=1W;UB z8~x2LzPQ)&wdgz83t>F^0D=%+TjjiM`*V~pZ6I0&)|5}5KSzJqiDnA9`g`j|>=9dn znvRVQj~@puyd&jqHC9nqkJMvi$E*eG5RSI&l()MtB**h DXwv;>^>^jh9 <1zV-QbN4%K%kg*4ko!}WP6&!~!bDNk7J^8hjy>VQYa5B51aV&}0( zFLqY&R-FKP+HN)>AU`8Mz1P&9N<#MYc{~-60NOAbdDxWnk)15a&u7fn3pJBuyJ*CU z3=O!~dFZGb16L7xi6k7MlXbUmcll^g?%Vb{hVzwKj`DBjB47BC5&PGt) 2d5F1+&4CsfY*Yo}K2yMS$kHVFxuiwNZG-uo-e!TzH zpbq)JOCYQ@VWofwY3W4YaAbxydLnGT?L(xXvIZCD6-23lF+mU}XtUN{3KNE!G?&UU z#vRk) uv0E7U- b_N^=`}wvFG3I#Bo*P*4Gi$K5YJf`#`Y9sfMdM*pWf>ZNnqdY1vZ$4Z5dCy6 z;lf3gG_^E0R~aM>CisOG7yPSLj8ppt;Q)j)@NLH8!A)o#?M_c;^@$>~yohzF$01f3 z@%NOs`Y=;{e9GGz1Wut7067X$=lRy(@1BAbx}qI#fx!M;9Kp(RL3VZrXXj0z3&ob0 zMo20LY_$?&xd5*c*5`7nc~dA-i1Ebuhz}a<+wb-Wn>pcXN>4$4kK6r1T1gx3)J!Xe zq3GDd?U<(+Y2b8t_6!fLOW68blXzx(aYljI>TvupcyOP=K5(NiLFMWicXYl!ulAsu zQfzGO>+3pCZr^6mb1GaZzkeUXk3yn(mqn9>dx{g#m&V`|j}8HA%Os9}Kd;44+FC?1 za$nc6Fq6rTg@9UZ3W9$G+09|m9F95OYfN!`It-@t7hPa516K 89@Tv&%_4Owh!!%*n5wY=8! zmbvt+_elOmZEY<=*ve~bp}rxBU(C%tO4ve_>4%x@+96*--2rpyIqfYiLN6~M2AR(Z zAmD2VI(%4N-RX1N@iuBAWY(Z!>7&!HU&r?Gh&rHCh_!z5Bmxa3#&ncA;R+Zch$$&J zz5R2&GNYa`#8GHaX$X<4K5xLDF8SR`0yByBJU%MdrMrS(?c}?N` IW&^kDEAOeWelqZu^{`_*U ss^D;C!FrRBk2Govq&ZZHm8}G_cDC6RKZFFB9SO3BeN4x zY-pF?%;L6GevqPk%Te|_5( Ql@pppiK)Q=IhsQ->`felrF+0a8K{CwvOI_1{sqU7O1nF zft~=eEM7i77)>7gjl2A3erO_Kjj^?r75RW@rgqh@Mp?GvSMZJe_%YQ*ZB&JjnzUJP zp;h#BF XJ+R=U@_`9Ls4 QEQtCW N{SOh z;59{w<{R$R>q86oZr^_9KS`0Ib;8dNR?-OU_!}G D|H?K^(-9+PctKoo(4`~G(fB(VN+Kug}_#F3f zuWX^$J9g|dsZS068xonzHc*^G{;zuNp@q{Mvf1UrsU(Mre3hdN3S{FdYHER-8_V!b zS=Al4*jCkOk>pV44SzFf=?Az0ip019iXqOHD44zu+&hHZl`5E9_jsdm;^XkOe-lIz za{(A>P-Y~*gN1)K_6@*pd`rOQMgY(3b@O%mlRfT~cDn%Uf)w}4r|MiwgmMW{L5T6f zIw)mKlqy@@9BxU1_Kk(2@_J@Iw9`7f%A)M19j(m<6werkzuCVnC;?9enqQ!|lx%OI z8yX+?QzDS*f_y=){So*b$ZCiWpqXpjXl-o;vh?WC-wrWo-J)Rm48v15CmkImJWFfA zV|j?c5UWj5X*NBN*Ff?QL6o_iN1QH~>ZX`>Q)X7=5fp^U`X6MG0gzJ`^jH{E%x=L? zOtNpHR3^@G+EdP})HgCV^|_iZ$r%f{(?Sn)BRw7YW^t`o2j=U;|B~Q%9B>J1XKTC@ zLmgx^Heo^9`txGzBKDrP%58A464An~VWq5
RQ){GZi=tqzo3Of?>(eU- zQJI+7G^P_V4aLc}u1PP2MIv^vVrMk_u3F l z{acC$aO2-pxG%tw&Xnw^+A*FhR *GBp>`t6GNUA809Lc@p#wANW5f zO-Jz@R87;%!WDpX)One%v5IV&9#J{*wsrXs!!r-Bu%&CE_Ejz!Y;B@pirs(Hv&{Ws z$g-xt`fIU9i#~zJ!d{fZz!pF_>T7s-xUH=%%zkeI{QUgTSK+R4aw3Fyld^blyFz|a zxEBk$umqH+AcsDX_PwB8tLV}*m{BnOz*Y*fj}#-j)-68#(5l0;FXQF@gyLYRt1zgP zW0w6)_g)H%hLMIjQX3# 7e;_~vi#ja2@fxIpXjygP z54qc&Rt26=t$?q^FnhM32i~vGXP#n$$Ven6B^^Q#Cq}xAAufrp3RJ&Jzl?R8nw_Ly ziU(edW3tw*mamr0X&q1}-UIMjrMC3D%d7K6thQe+!dGf-V}l>^Ea>YsG ^kaAfUens)_0mMsw+jL!@X- z6!xYjlJ*x_cUHy#BCzKWUl l7%cz|+(9dG3BYik~f#jr;J zTRn6S)6zw#@n8|0lD{oR@`VXrTvVP(@(L~1p6=gxq{#IITa9ok-4qhFmNd)Eh;4{Y ziK{`a1mKKtt*sUo1Jg3LXmdAN#8{C$y(C3NflS?lh2U28@hSQA${h5O+qcgKYO_V! zeMwdRJUboUIBivs*4z$i-33d_NFhXZbr+#Csnx9<0;v~MinS)}Z^fQxQ0VS-B|Tni zdNm~u6^z@ql|<%_4Rdd+svG}adHZ$ 0&x<2bCT;j|Pk8{nDnQAubr)&F&6IcVFDJ<$^r{vgbe9;O31ttl@>H>oOO(8S@! z!+Aw`awj2&UncZDa0UDR4jx7tdG6!_mJLn`CXsAg@Gp>T;XC1O-&Vt@)TZ ;4()g4sZeKRWXZ``fpTbsBBou-jv5O?&{Sv;S15RzqHP z0 sRW+#DR$=;=t2xd?MSVK{MaVq!P1x9_Q@(t-R1VL!9z z7WSEv4@XsaiW!@uV5xY%{_ZfMqnjho&HNyrzBrR%K}s_Nu1j^L=1wW~u)vNk(lUmP zV41+}TbJVEu-^ wOC|Nx3O~)DeB>?TZ-T%1&Z W&g2Tz-1?wdMP(Hd= j zbd4Ly!$w9?ecqgsZ1y@Xr!uLAKb^_#{w(ZgU^pe1Yn8CWi8o!HO-qsSk}S>}I9YI4 zC%+`EibM-isO1P(UV1vxpm9o2g!102(UyOES6r8 PhQRG!vM1u#U#j z$NJ_ONCLN(g|^636`Z9W%_nE7D^M`@A*Z4L8K)2D+j95oz2dlM09j*9#%36@kIfb` zoJk~Fw?O^Wej6J`^Vq2#`_s=_7Wf~S(wFD?+?7s(g8EZn)spBR`PqTTrm5#l!VtI$ zmJ X2p%4O|& zi<}y>w8MVK6*IEve&&%a3}S&rh)D+Ct*Nd)86Vk&a57-TVo1frFc_C-Ev@`aw?gm5 zJxc 6JYHO112KaUKcfQe*mU31=y24`WmIl`GGj?_;1q6*InMZ_~Wl4OJc1 zfzaExB-GJa4-ZL>1VFnoDz`-E&U_}7(B`B>@Hus=MZxW#8wBCEB`q7Y`BU-ak<|Ic z#m0BuA~jqW`x{XH5Pde#0NWz8VX&g%-WO(V9A?uV7cH}RmerzHw2PJMwJ7P#)A?Q> z6Dp{i0i68MA#JSsX$>CSV{&74;|3p(&_2&6AIRT;VDKfGQL&oN!C_D4OOFvqt$|Rv zkM;&;WM!?w#{3awha{4 s|Gueh;i|zaILSEB6H}i^ZOgNM>;a#>Hi5-WjhCbo zBxOy3KL+k<07n3)k)>q^vgpx!V0(&7<#Bdh_PUHe^9s(>-;MV^%w{hujEQZHHc>=p z{*<3{7ua+x3vs{%{qPUuqKKhGgCjxp+Mft8WN>JR*4Ao`<(1Y!k?GN+)8UHepHsPz zdk`^Iw#2i<$S0Wdf0~^|d)b2h6S32m&jxwV8}TcWGyC`Nv6l9@h+Zhg+%vL=DaM|f zXAC!FG9%)~7Z(=tw9kFnrdUrhlwqJ4-Vyc0WqOBs!G_L@Gr2ZE*tyu*qaBtK#+!RA zR1V&@n7i1gAJ^;v5OXvMtbW(0Ge}`)q-k17^O8D9of)C=@nj(XP%XXEn39o7Mr-?? z-u}euVn wO0<#@{fI-71c4xw@GR8t?zr^xff9 z_y7MKj>z6KS; xHY^Pac_v`hK4K?3)O`~brqNnXcx9m@*466p z<+84jResjfAZYq7t~JiT%Y~9V39awbQ5HYqWU7!5UvZ!H@#A~p|GO^sBeu)U+tU-9 zyXS!|hBTT*WP78SFvZ@q;5uo;3|$r~Yq-`up?-~scPxYS8L_5>O8r3z0@sjU>j}dJ zP?n#6^GEV{Ub#B2m`YNtw6aG~hod8lkvl0qE)LRSE?!=m?~?I7jIPv^H>zgm{t&gb zzUVyR7VC)qyYUZN^QFR6aI06#miouXo3VHT6c^ndJ=*_$86Qb2(oW1T3!I^8UyAyb zc}3-)yfXE4h8vImz!!gJCZ^6+Jwl`7c5ZHc%dZe+VNPb?zz%*@u^l}w-2SCz#fH^U zD%86=T6wf?Mvd+b&ETmQ#jb>zGpa4NARl^Km$qb-!HC>ylT=I}?aY%(*)_ZV3#wU^ znC)OU>?`|h+Av~zD*z^k+m?IU`?dC;ek~U`<-C~Z_Ws6J!#^$!!infn_0JKJ{{89w z|M}9dFB;$A;k#~*@f9d3NJ=1Zhj9D@# Ze(xoF; zWXxt*&nlkSS9)k(#ldslz>|7M`t^fMhN@rEJGLC8OD2;PV`kVEtSYT&_7lC3w|K e}| zdPF#6BKp%O7cF0>=#~X-vZV2;l)wKby( A8vjcqA;^PS@0f$F!ASFhm`J=NkW z*bcxW2V+LYeN-U7!$Z6=mv~SPT)RL@L>Eaa$aRRG_ %jvg(%d>yKxf*I1qdo%cT5I&%IoT^ zx!SOjAy<#&SrYRx&S!9&oHpBf1dvUB)hIq!L(c=a5s_QWH;}95a-?;+B!SkIn0*IH z-0RdusJvNN3&WX-RB&>EzBv;EgAvm8t6hlS49xZ^8{3CI9E|Lkm%mMC^lw+#zIMKE zjxi#yhKDhsI2$YU#*+6-fuay&zB=*$wE!f|04_~rYa1KC0Op1~Q8V>~eTPIY1Wj`C z=h)|#ET7M`8C>NQNHv@7Z=p7QdwCNxMD&IhTO7b2NZb(%lztse?Wk?uzCA79ScFY$ zmi@vJJVKiTX%UoQ2lVia0(rm9 HrYn;I?T4;fAB1@s>h|}wRU12S zmKumAW|_K@kk$V7t&QK82~UJ{q2j z1nR{kQhAfJ z>-D_)r*nEKHg!`vSF&UMEMz#&M(vZD{HdwkdkD}}S_@C7UyNL_&t6xPTSJQXPoa|; zhCAncq;ohpd|-{3iv?15_T3|CQBm#k0a^m}&0u_JpTH``p~te_nkV&yi=2f#P2)iuf!Vbt`3Zj z_N*EzLNGEk^SLmRui6*5yQ>^ox&b+UDF)?jF7s<)VAdSbe%=0rm6a7?UH!%}uXP1@ zQZE>7F72S#fhY*mzKNonq_{YZB4O`_Y5q-KT51QWSF~TT@F2Sty4*YyNwUOJ=mw5y z=b}LMQHo^k*s&Q4_7&s2;Om>Ft$R@;%A}jw<@R_1yBl&-5wzu`!QO 4Xwdb9-l7bL0Vxh8 )RZpWPTxxn*8Ih417ni+4j8jOme4APOd8%-e0nf^q&ot+ z)C~Gz1GPGWQ^2h7_eEULD8f4>-bOT`&J2jMdA3d`p=&XcQa`Oow9!mX4l$V&HN3Nj zvs&MkQK9TuMYwnt(cm^(W++)wi@pkc{rCBKcoKs4wDT>0hd$Y2Iwm02sGnr-lVSi| z HPyiqs#ud^C !MEN!`@6mRxa{F}k#`PxA@iQ)Z_3%Tzn zu1>_aw(7;U;Ph~+dygp_MHVf&=xK$B|1o3-s=lBR8XFrax_*qgXbE*g0*kALE)~qnL3K_#IX&*rA2YP`nZ#LCy!yWERK_0iv__$& zboZ{yZ3MDzR2m`Eh|gO(O=RKb1 qChAL`$Uenk3T>zBl2)qtmeKtRj6 zO-zn%r%r7k P^d1r6>__F(K;EETxPA97 zBK)M1osyN!sNLx1cPontbfE2iQrr)#Uwdumsv%cWult*%^Rb@yv9^Ge26}8cE;T>m4*zRWbGvg-y~r#B*M1)26(B zgy?1TKU-A2L5Byz^vlZcmX=ZraYPA6nF+_AzRs%69&J9k)EM;inqq>xmxL`8`{0=% zH$qJt7-@_I*aN<}{I9>M0iqj9eIV>Qg<7k*gJG+!mDR-D7@=>)EVKlGX7FBnojJoy z7t6;08fY*oMxsuXo*+37uFy%;+;7WQo)@J47_(#mJ4ozvQ&MdI_Wiq}8a3BXWozVW z;*Ld2!s @{Q;shkYKF?G7!fD7n Y) W4N(l#=y)Q&v{Ae-AU^AQD0q2)Zxj@MCaWvWDsC! z`Nu2|r*g872ff=O7{jQ}Zms$?v%L(RSCEHT-OYBnkJlxT znAze)!v8|)*+KGmD3E0+4EgX=3kX0ls`VNb2ikv#I`n+P8eHl+v>s-6hcR)3en(e$ zKR#0F$o7!;Os-72^?nDCkAZQEkK;eclOcZqAzqevu8j3FJ|O{tV8&-z&>n#E# GsZg7Dxb+|;a|wM9-2t3Xh#g0wBrx&M{%bIi@!AmT<94R28>Qb|{<|TZ zcB``T3=EKz{Fgs(sjCfI52Yi+h#o?K0w%|cc_yfe((Z1d2vbm3+w+Za^0BWBS>c&s zI-;zya&k7;rzjO7Q1y)ed0j`*Y{WjXmAvv%M|v2%Tl$qN_`dRS&qbmp3-HWM+ir|c zVNIY?(U7JoFQ0l_g59*rrrHIlQ`A4%RZjW(boZ`sUC@qHv$PD>ApEB+fGwUiZ4X6$s{V0Bf<+I;*gAln+)6c#_a7;@O%T zQuSs(k}qYMYmoKxZw0Uu1rSS08t|^=>O0!{Yg9Rjcq;boGPJb#OrQwUI=7A~6tG~u z)I_JWG;z=AY8$kZ*5LY_8-JcTB$)n%rt!h}Y@Ll 1(`!5wsKg5RC#{=LVB>}_mpAgY*TeKr#a5@K#av0b4i_C*6yauIV0Pv!delbd$7 zwhR-@EP~&%{J5dpz}$4g!J!Zw4kzao=+@jDz;I&p&dJHq-HR!QGWLOag6Om175xw~ zZStY;-+pl#rl}^>C_?3^7C6mjb}UAu8%?5icM&7iqZM`a3FuuxZ1@9*wwFdb|4-!1 zwhw`)yX55b!U!H5KZFnrrl>gIfiQ0<*C3IDUKyGSFfdZdB{LB8Xa;;`vYY^}>Eq*r zBzbjLH=*DoqF+Am*47y%Kjx=4Af!uX^abGt3VM0~DBJ*bV1JL#>NN+}4~J#wo#g^B zl1EwLWHcy02j79=b>a>^Lu%6iZs_nn(zAV8b^O?KwbSS`)mtY@st_8(H#dLZSH4~O zee3wjBR=>G2yr9@f?&r)E6KGXJ?g~!kcynQ6-I@xDtAtLT}ygzbTd+YBim>*Xwc|Q zNPDfvpvsZS_8>`yRwD(|LP^?gZ>wls+PD*PIwEmep;ZT)>OF){`-qI)Sv=fsH}-V4 z=wdIpxL(98;TS}i+A_CRc9Ndn+AgDkBN? XFft~W{4JKxyUVsM&b`_ z$*JjVmlKYp-WzsJJMbTxEuc+_;R4B M%@$KSjVlc5i7;vL9?C?4hRnN7ujdW zn+_n{hE?$64U5QM$^AsBD%vZ>>4Io_z-^1SQ9cKrKHcZd)>)aUk#YjxPE?4x`}m-V z@O1XO%j~W6i)0(5TaZf`B9~K%&mX&ASEC4T3JQ>@=#&m(kk1uV(5v=1g5B){qWQ*j z#}SE#j@)B7Ctkm%^W%#~?pPJdtTvY4KzR~<_#-`(sGcWK#}xeIE#w#T2v{$vi>t}n zCVbnVe55|WLtmfB9Ld1Q7@&TQ(k}_8^e!Go>L-VVIr_Y+{5HL7-=TzN)YpU$p|}9z zX#eOFK0Ip@5)=e<0-+=&W(%Apvc#m#ePOs58cI@%Z AwyB?&Zrt~b(d(8 zwtsed8m 2 zdPMV^@02`IxFsg|%C?t3;-a~qrF5A{XK_ej5UZ@8r8yV8@?bZ)wr$&G#8JD(secX! z`s2r|kTU^<5*HT-{PFcbZQa@Ne;C@8)zvW%{J|6qtP(+qzkaPPP96L+T@FG8C0XT1 z^X)2OSVKUu`Q2d~-n0(bMk%b?ejye=&;EK2anZb(?f$&aC%kk7Puv~GElxTYGf8_Q z#`flwqHxwn9`_8=_w7qetijTMa{Uu8vXqbsiuo9mDPXZOmrs=Hz=9r(UGvvP=%v>3 zjp+XU)5}YI9)ZLafo L`PFH zgMAIYWXxbt3u-Wa`&(_Pv~w%-lBbVP_ZR aR~Hu~6t*~#xeDj!&qY0l9~FNv z-&`b~zLuLCf;cg_v00pT!HHdkWE7VueIkNPF5 Nq0S{OavheV;@&s7 z4g{GJAkcgviV#;2-eAS`yTUnVQ!*#KLduPA=zPqOd-*DkKhGxr!`eo52fc8Ivbaqy zME@rdG(gd6Mb8~>_{#D)10m%jK8F9ra;vJTjSLRf_ CO zbcKTAQ oi>RIP-bduj zksjK3WMZAxD6_wVw~5=8Nzns02yCGmj2EBss%_>>+$j+TC`s|CvgAM_)y~)c7$H~_ zS`rpqAIQi|L4G=_m)P0CzXB(KHPiH{?N!7IL+?+$hkhufht$2pv>NOVlmeP4N4m!C zwM?tuq4ex+ZW*Jx)dG~!6bJ>OxYN&X6DeI3P|=~JIr^P^NRoTDTH@15BOuB{L$S(o z{=#Nz%Ic@_f#UtmDlSf=5st`jb3v)Np`+s%R*Py^whb|&x3T~T?Ro%M(7Rv+di~}N zIMjRh?m;64yDv~{WX3}q4)+%RX1hiR+f@~W^nyCx EK z=-^x_P^9BAzHmXUo1TqV%)RFh)dER*xVujDlveM~|0FLZs(PF%dZBZGfoH3OjyYGO zW0i!_gbZFP2yB__T|VE36Y7LKuXc@zdFT<#HT(L`dfyaf+LQ4=cwcOQ?4uZ?gjtoQ zylejeJVa(mhntF-Tqjd%S{mYke(0{N54Th2Ka<}P!?kp^*9@MBH{YRkd!SZ2h;!>E zV`VCaL9ie5?)Vx-!SgF*u#NDiv;)7%a-6Wf5BDb`Q-IXdG%?6L5s`Cj68F?|l@W4M zeU4hEIT28CV 0M&GckfnH%X?RKd-eME_QeHQ1U;4U_s>uv zpdpMjZ9zd<`j{>^btuo2jPNu KbzKWC_1IARvK=eNWZJ=0JC)E7 zh<7nYg*F!P*}X{HJt6<#6``t!P6Y=Ieh1Wdf!MM#H>VyO2JxxgjhJJYv!E2f+DD%O zcBibo95(CIC8wQ&Noi?d*usIJVu8N5W#Bqbq>BYrQY_OJJrOpltFI9XK0&juE#yD| z0BKiJ`?`+Qkm#JVtW$PN)FiXgY_qs*=JCKlY%LUF5@t`_lS#=?X<9 FP10n#f;2Cl$+Mtsmve8$`R8Lta7g7zRN0n*s!5-qNBdU^Vpn??>ZM)niC>)8 zv}l0eJjJt&VY4I%cjM3yCnskB)QxW)Ul=H~(~CQ=_qW*!QgT>jmC87(t~rq?;e{?z zlT-7`X67>w*gfklWozPMer7_Pjf4=${(s!TNdWl?0%5YUxCA=2YK?uoZ;jb+?pke> zYNS(a{~2_;_8lVUH$SpQQqkhars(1j42sVj# )^?D=2bb8;#+&=ocP|7NcyN9QU^KC zQ>_hq@4YD>m8SKG$3a8PF|RI_`a?=OTRUf;e&w{IRs!$EG&9H6F??&PENniLRAK^O zk!?Bp`ZW)pn3oG2-n$EK2_!j|#B<9FRQ3t6bbzEkW@e^%bfQ`Z8hgI?~aeOSi98ye(rK|MDfbaF_|5FOnB#&cJ90d zNAyd=_`UyyIqg(b%#dv8c}08Szem3v>X=UGoD+Ih2#q~LgPS3G$31p|78<##zNy8Q zy>0j7jZ5UX-g0mIpzsDZ3;-#3Qr~HFlAm@ix6Cm0a=xH4Z?b~j4ti-6(|fY;)s~c# zRibi5j5snDA2?jv(=ygErww)ZVrra zcwzIb%51C|MmC^do&DIeTOtd7g%#K4rHXq18yGV8M za1+ljg-&~qOUd&GGnG#XZ t-2;jL$oBMiLV{^+uO2ow25TC50z8mAE#u`oD el!Rq_9!DD$R~zm-G`d$c{ka3E zva<3BP97hX=J9b9Qr@^x6Y%*eJuC^@TBg(rlX=#wns2M&43kPXMl(xK5Bityc6Rja z?i$qo8dzF-9y RL6oLOt_&Ky5J4A8L6rK_h0 zhL NfT9+z0xJTK=^ekiR|j; z69nrD)+#wUW{P|JcC-Ux_DwIOVJc;!;+<{o?rkkQ5cgpM3Ki#CJ4*F51lqvsd|Cke zp<<3^3(JqO#`kVY>|fa}O{|?s)JtU^23mwTN5(>{c; 6)`LW)+CoFYtGDT%Wyl+Dy1<;mp^un@Qgw@y}bcPI?nb!wL-$0u>O&i7SEI1M)45 zI7<|X6L3m=xP?oBw3)=B<(Rk;w||?B%V{5eZR{@X4vo(&Q>AlwgsT>U{`TA!C%nl> zty=lJ0Y=*ICtvY-9UA^qy~-liM6&2@%NP;(3qi